C#ATIA

↑タイトル詐欺 主にFusion360API 偶にCATIA V5 VBA(絶賛ネタ切れ中)

点で面に接する平面

ネタ切れですが、ムリムリ書きます。

先日お答えしたこちら
Solved: Can not create a plane by setByTangentAtPoint in design history - Autodesk Community

"点で面に接する平面"をAPIで行った際に、履歴付きと履歴無しで
結果だ違う と言うものです。(履歴付きが正しくない)

結局はいつものテキストコマンドでの代案を書きましたが、
こうして考えると、GUIAPIで内部的な処理の行い方が結構違う
のではないかと思います。(=異なる結果が出る可能性が高い)

テキストコマンドで行う方法は、過去の経験からしてもGUI
処理する事と同じ方法をAPIで呼び出しているのだと思っています。
デメリットは、速度の遅さとテキストコマンドの仕様の変更の
可能性高さですかね。

最近のリプライはテキストコマンドばっかり・・・・。

図面の自動化

Fusion360です。
ちょっと前に導入された機能なのですが、"図面の自動化”これ凄いですね。
Help

手元のデータで試してみました。

寸法値が線など他の要素と重なって見にくい部分はあるものの
十分ですよ。寸法をチマチマ入れるより位置を調整する程度の
方がはるかに簡単ですし、寸法漏れも相当減るはずです。

これ、個人ライセンスでも利用出来る機能なのかな?

選択セットを作る

ここ数ヶ月、あまりFusion360APIを触っていなかったので、
意識的に触るようにしてます。・・・忘れそうなので。

ちょっと前に答えたものですが、こちらのお話です。
Solved: SelectionSet.add - unable to get this working - Autodesk Community
選択セットが作れない との事です。

GUIでは選択セットは最初の頃からあった機能だと思うのですが、
APIでは比較的遅く対応された機能でした。

要素の再選択を手早く行う為の機能で、多くのCADでは
類似した機能が有るように思えます。

全く作れないんじゃなくて、上記の青く選択しているような
コンポーネントやその中の要素で選択セットを作ろうとすると
エラーになります。
APIフォーラムを検索すると導入された頃も、類似した投稿が
ありましたが、未だに修正されてませんね。

最初にリプライで投げたものは新規に選択セット作る為の回避策で、
テキストコマンドを使用しました。

二度目のリプライのコードは、選択セットを利用した際に選択される
要素を変更する際に、”APIで選択セットが選択できない” と言うもので
これもテキストコマンドで強引に選択させました。
(恐らくこれが書けるのは、世界中でも数人だろうと思います・・・)


選択セット自体のAPI実装にも問題はありそうですが、もう一つは
proxyの問題も含んでいるような気もします。

APIフォーラムの質問のうち、ん~1割までは行かないぐらいですが
proxy関係のものが結構あります。
(バグじゃないの! と言っている場合でも、proxyの理解不足な
場合が多い)

選択セットを実装した人もproxyの理解が足りない人が実装した
のではないのかな? と思うぐらい厄介でめんどくさい仕組みなんですよ。

再発明の失敗

あまり細かな事は書きませんが、こちらに僕がupしたgithub
リンクがあります。
Fusion 360 Internals: Trying to re-invent Fusion’s GUI (and failing)

昨年、記載された御本人から "あなたも興味があるだろう" と
直接メールを頂いて教わりました。
知識としては覚えています(理解はしていない)が、あまり
興味はありませんでした・・・。

アクティブなコンフィギュレーション名

昨年の後半だったかな? Fusion360コンフィギュレーションと言う機能が追加されました。
デモの状態の時から知っていて "おぉすごい" と思っていたのですが、
使い方を知らず・・・と言いますか、試してもいませんでした。

ざっくりですが、どんな機能か?と言いますと、一つのファイルに複数の
類似したデザインを持たす事が出来る機能です。

詳しくはこちらです。(丸投げ)
Help

それなりにやると、ここを切り替えるだけで形状が切り替わります。

凄いですよね。


最近になり、APIにもコンフィギュレーションの機能が追加されました。
そこで、こちらの質問を見つけました。
Name of the current configuration? - Autodesk Community
アクティブなコンフィギュレーションの名前はどうやって取得するのか?
と言う事の様です。確かにHelpを見ても取得の仕方がわからないです。
(そもそも使ったことの無い機能なので、何もかもわからないです)

で、あちらにレスしましたが、探し回った結果こんな感じでした。

# Fusion360API Python script

import traceback
import adsk.core as core
import adsk.fusion as fusion

def run(context):
    ui: core.UserInterface = None
    try:
        app: core.Application = core.Application.get()
        ui = app.userInterface
        des: fusion.Design = app.activeProduct

        if not des.isConfiguredDesign: return

        configTopTable: fusion.ConfigurationTopTable = des.configurationTopTable
        actRow: fusion.ConfigurationRow = configTopTable.activeRow
        ui.messageBox(actRow.name, "Active Configuration Name")

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

何となく、Designオブジェクトにありそうだな・・・とは目星を付けていました。
configurationTopTableプロパティからゴリゴリと って感じですね。

イナーシャの取得

イナーシャが欲しいのですが、ササっと検索してもProductばかり見つかったのですが
だったのですが、サーフェスで欲しいです。

結局、全部入りのサイトがありました。
Measuring Mass and Inertia | CATIA V5 Automation

消えてしまうと困るので、お借りする。
まず単位はこちら。

メートル法っぽいです。

で、
Product(Partも) : GetTechnologicalObject
Body(形状セットも) : SPAWorkbench.Inertias.Add
で、サーフェスは直接の方法が無い様なのですが、新規の形状セットに
突っ込んで、形状セットのイナーシャで大丈夫だよ と
記載されている気がします。

そこで、好みな部分も有ってこんな感じにしてみました。

'イナーシャの取得
'''param:surf-対象サーフェス
'''return:array(Inertia, HybridBody) - 失敗:empty
private Function get_surf_inertia( _
    Byval surf As HybridShape) As variant

    set get_surf_inertia = empty

    If surf Is Nothing Then exit function

    dim pt As Part
    set pt = get_parent_of_T(surf, "Part")

    Dim objRef As Reference
    Set objRef = pt.CreateReferenceFromObject(surf)

    Dim intType As Integer
    intType = pt.HybridShapeFactory.GetGeometricalFeatureType(objRef)

    If intType = 5 Then exit function

    Dim geoSet As HybridBody
    Set geoSet = pt.HybridBodies.Add
    geoSet.Name = "Temp_ForInertiaMeasure"

    Dim objJoin As HybridShapeAssemble
    Set objJoin = pt.HybridShapeFactory.AddNewJoin(objRef, objRef)
    objJoin.RemoveElement 2
    pt.UpdateObject objJoin

    geoSet.AppendHybridShape objJoin

    On Error Resume Next

    Dim objSPAWorkbench As Workbench
    Set objSPAWorkbench = pt.Parent.GetWorkbench("SPAWorkbench")

    Dim objInertia As Inertia
    Set objInertia = objSPAWorkbench.Inertias.Add(geoSet)

    On Error GoTo 0

    if objInertia is nothing then exit function

    get_surf_inertia = array(objInertia, geoSet)

End Function


'指定した型をParentから取得 - typenameで取得出来るもので
'''param:aoj-対象要素
'''param:T-目的の型名
'''return:AnyObject - 失敗:Nothing
private Function get_parent_of_T( _
    ByVal aoj As AnyObject, _
    ByVal T As String) _
    As AnyObject

    Dim aojName As String
    Dim parentName As String
    
    On Error Resume Next
        Set aoj = as_dispath(aoj)
        aojName = aoj.name
        parentName = aoj.Parent.name
    On Error GoTo 0

    If TypeName(aoj) = TypeName(aoj.Parent) And _
            aojName = parentName Then
        Set get_parent_of_T = Nothing
        Exit Function
    End If
    If TypeName(aoj) = T Then
        Set get_parent_of_T = aoj
    Else
        Set get_parent_of_T = get_parent_of_T(aoj.Parent, T)
    End If

End Function


'ディスパッチ
'''param:o-対象要素
'''param:T-目的の型名
'''return:CATBaseDispatch
Private Function as_dispath( _
    byval o As INFITF.CATBaseDispatch) As INFITF.CATBaseDispatch

    Set as_dispath = o

End Function

関数だけなので動かないですし、VSCodeで書いただけで動かしていないので
間違っている可能性が大。

BrowserCommandInputへ初期値の受け渡し

久々にFusion360です。
こちらを答えてみました。
How to create a `BrowserCommandInput` and populate it with initial data from Fusion? - Autodesk Community

"ダイアログを表示する時にBrowserCommandInputに初期値を渡したい”
と言う質問だと受け止めて答えてみました。
BrowserCommandInputはダイアログ上にHTMLを表示させる
コマンドインプットです。
全く使った事が無いのですが、仕組み的には全くパレットと同じです。

コードを見るとCommandCreatedイベント時にやろうとしていますが、
タイミングが違うんですよ。
CommandCreatedイベント時はコマンド自体が出来ておらず、イベント
終了後にコマンドが出来上がります。

その為、初期値の受け渡しの python -> javascript の一連の動きは

pythonでコマンド作成 -> ダイアログ表示 -> javascriptのDOMContentLoadedイベント発火
-> javascript側で"HTML読み込んだよ"と送信 -> pythonのincomingFromHTMLイベント発火 
-> pythonで必要な情報を戻り値として送信 -> javascriptで情報を受け取り -> html側に反映

と結構な処理をさせる必要があります。
それともう一つ。BrowserCommandInputで表示させているブラウザは、javascript
非同期処理で書かなければなりません。


フォーラムではファイルを添付して中身が見れないので、こちらで
公開しておきます。(こちらはスクリプトです)
まずはエントリーポイントとなる、"BrowserCommandInputInitialDataTest.py"

# Fusion360API Python script
import traceback
import adsk
import adsk.core as core
import json

_app: core.Application = None
_ui: core.UserInterface = None
_handlers = []

CMD_INFO = {
    'id': 'kantoku_BrowserCommandInputInitialDataTest',
    'name': 'test',
    'tooltip': 'test'
}

_browserIpt: core.BrowserCommandInput = None

class MyCommandCreatedHandler(core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args: core.CommandCreatedEventArgs):
        try:
            global _handlers
            cmd: core.Command = core.Command.cast(args.command)
            inputs: core.CommandInputs = cmd.commandInputs

            onDestroy = MyCommandDestroyHandler()
            cmd.destroy.add(onDestroy)
            _handlers.append(onDestroy)

            onHTMLEvent = MyHTMLEventHandler()
            cmd.incomingFromHTML.add(onHTMLEvent)
            _handlers.append(onHTMLEvent)

            global _browserIpt
            _browserIpt = inputs.addBrowserCommandInput(
                "_browserIptId",
                "Active Document Name:",
                "index.html",
                50,
            )

        except:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))


class MyHTMLEventHandler(core.HTMLEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args):
        try:
            htmlArgs = core.HTMLEventArgs.cast(args)

            app: core.Application = core.Application.get()
            if htmlArgs.action == 'htmlLoaded':
                args.returnData = json.dumps({
                    "name": app.activeDocument.name,
                })
        except:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))


class MyCommandDestroyHandler(core.CommandEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args: core.CommandEventArgs):
        adsk.terminate()


def run(context):
    try:
        global _app, _ui
        _app = core.Application.get()
        _ui = _app.userInterface

        cmdDef: core.CommandDefinition = _ui.commandDefinitions.itemById(
            CMD_INFO['id']
        )

        if not cmdDef:
            cmdDef = _ui.commandDefinitions.addButtonDefinition(
                CMD_INFO['id'],
                CMD_INFO['name'],
                CMD_INFO['tooltip']
            )

        global _handlers
        onCommandCreated = MyCommandCreatedHandler()
        cmdDef.commandCreated.add(onCommandCreated)
        _handlers.append(onCommandCreated)

        cmdDef.execute()

        adsk.autoTerminate(False)

    except:
        if _ui:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

続いて、表示されるHTMLファイル(index.html)。
(javascriptもこれに書き込んでます)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div>
      <label id="activeDocName">Unknown</label>
    </div>
  </body>

  <script>
    document.addEventListener("DOMContentLoaded", () => {
      let adskWaiter = setInterval(() => {
        if (window.adsk) {
          clearInterval(adskWaiter);

          adsk.fusionSendData("htmlLoaded", "").then((ret) => {
            let data = JSON.parse(ret);

            let activeDocNameLabel = document.getElementById("activeDocName");
            activeDocNameLabel.textContent = data["name"];
          }, 100);
        }
      });
    });
  </script>
</html>