C#ATIA

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

表示されているボディのみを取得

表示されているボディのみを取得する場合、ボディのisLightBulbOnプロパティを
調べるだけじゃダメで、Bodiesが表示されているか?コンポーネント
表示されているか? チマチマ調べながらやっていたのですが、ちょっと前に
手っ取り早そうなメソッド見つけました。
Fusion 360 Help

類似したfindBRepUsingRayメソッドもあるのですが、イマイチわからないので
試していないし、パラメータが少ないのでfindBRepUsingPointにしてみます。

こんな感じです。

#Fusion360API Python script
import adsk.core, adsk.fusion, traceback

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)

def run(context):
    try:
        global _app, _ui
        _app = adsk.core.Application.get()
        _ui = _app.userInterface
        des :adsk.fusion.Design = _app.activeProduct
        root :adsk.fusion.Component = des.rootComponent

        
        showBodies = root.findBRepUsingPoint(
            _app.activeViewport.camera.target,
            adsk.fusion.BRepEntityTypes.BRepBodyEntityType,
            100000,
            True)
        
        for body in showBodies:
            print('{} - {}'.format(body.name, body.parentComponent.name))

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

3番目のパラメータがイマイチ分かっていないのですが、でっかい数値にしときゃ
みんな引っかかるだろう作戦です。

テキストコマンド3

こちらの続きです。
テキストコマンド2 - C#ATIA

ここまでは順調とまでは行かないものの、新たな発見や今まで手に入らなかったものが
入ったので、苦痛ではなかったんです。問題はここから。

一番悩み進展しないのが、各Inputsに値を設定する方法です。
f:id:kandennti:20200605193540p:plain

イロイロと眺めているとこの辺りに該当するコマンドがそろっていそうな雰囲気は
感じ取りました。
Fusion360_Small_Tools_for_Developers/TextCommands_txt_Ver2_0_8176.txt at master · kantoku-code/Fusion360_Small_Tools_for_Developers · GitHub


「A」は前回のデータを見ると
"id": "Path"
"type": "Nu::Components::ChainSelectionCommandInput"
です。 最初の例ではコマンド実行前の事前選択で回避しているのですが、
事前選択無しだと、こんな感じで可能でした。

Commands.Select <InputsID> <ONK>

区切りはスペースで大文字小文字無関係だと思います。
この「ONK」が何の略称か不明なのですが(オブジェクト、何とか、カンとか、、、)
取得方法は選択状態にし、

ObjectPaths.Onk 

をテキストコマンドで実行。「ONK::」で始まる長ったらしい戻り値全てが「ONK」に
該当するようです。

 
続いて「B」と「F」の
"id": "SWEEP_CHAINING_OPTION","PIPE_HOLLOW_OPTION"
"type": "Nu::BoolValueCommandInput"
ですが、Type名から比較的予想しやすいです。

Commands.SetBool <InputsID> <0 or 1>

<0 or 1>は 0-OFF で 1-ON です。(ひょっとしたら0以外がONかも)


さらに続いて「C」と「E」です。
"id": "SWEEP_POP_ALONG"
"type": "Nu::Components::PointOnPathCommandInput"

"id": "SectionRadius"
"type": "Nu::RealValueCommandInput"
今まで気が付かなかった、、、Typeは違うのですが、数値の設定を考えれば
予想が付きます。

Commands.SetDouble <InputsID> <double>

double は 実数(pythonなら float) です。 テキストコマンドは不思議なことに
文字列でも数値でも同じ用意記述します。
(文字列を"でくくったりする必要が無いんです)


最後に「D」と「G」です。
"id": "infoSectionType","infoBooleanType"
"type": "Nu::DropDownValueCommandInput"
ドロップダウンして任意の項目を選択出来るものなのですが、これの設定を
切り替える方法が未だに見つかりません。(SetDropDown~みたいのが無い)

無い知恵絞って色々と試してはいるのですが、どうしても切り替わって
くれないんです。正直、こいつが最近の憂鬱の原因です。

APIで公開されており、非常に似ているものがあります。
「DropDownCommandInput」です。
Fusion 360 Help
似ているって言うか、恐らく同じものだと思います。

こちらの場合は、Listから選択するような操作になるのだろうと思うのですが、
それっぽいものもテキストコマンドでは見つからなく、息詰まった状態です。

ん~書きたかった事を全て書ききれない。 のでまた次回。

テキストコマンド2

こちらの続きです。
テキストコマンド1 - C#ATIA
殆ど進展が無く、気が重いのですが続きです。

前回はサラサラッと書きましたが、細かな話を記載しておきます。

1)コマンドID
ダイアログを表示させるためのコマンドIDはテキストコマンドで簡単に
入手出来ます。 任意のコマンドのダイアログを表示させた状態で
次のコマンドを実行します。

UI.CurrentCommandInfo

大量に文字が表示されますが、先頭付近に記載されています。
f:id:kandennti:20200602191416p:plain

2)OKボタン
途中を飛ばして、OKボタンを押す方法ですが、特に記載することなく
前回書いた通りです。(見つけるのは困難でした)

3)InputsのID
問題の一つはここです。Inputsとはコマンドのダイアログ上に配置
されている入力したり選択したりする部分の事です。
f:id:kandennti:20200602191428p:plain
前回のコードでは利用しなかった "チェーン選択" のIDは、
"SWEEP_CHAINING_OPTION" です。このIDの取得方法です。

これも、最初に示した "UI.CurrentCommandInfo" で出力されています。
テキストコマンドの画面上では見辛い状態ですが、JSONフォーマットなので
スクリプトでこんな感じにすると見やすいです。
但し、実行後GUIを操作すると高確率でFusion360がクラッシュするので
ご注意ください。

#Fusion360API Python script

import adsk.core, adsk.fusion, traceback

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)

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

        txtCmd = 'UI.CurrentCommandInfo'
        info = _app.executeTextCommand(txtCmd)

        import json
        data = json.loads(info)
        txts = json.dumps(data, indent=2).split('\n')
        for t in txts:
            print(t.encode().decode("unicode_escape"))

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

Fusion360のバージョンよってエンコードが変わっているような・・・。
文字化けする際はこの辺りを参考に修正すると良いかと思います。
https://qiita.com/inoory/items/aafe79384dbfcc0802cf

"パイプ" コマンドの場合、こんな感じです。

{
  "button": {
    "commandId": "PrimitivePipe",
    "toolbarId": "FusionAssetType",
    "panelId": "SolidCreatePanel",
    "beforeCommandId": "SeparatorAfter_PrimitivePipe",
    "asMenu": true
  },
  "commandInfo": {
    "id": "PrimitivePipe",
    "name": "パイプ",
    "resource": "C:/Users/<USERNAME>/AppData/Local/Autodesk/webdeploy/production/1f559bb8ae333199306b5c4f1fe680c6eb7ab9e0/Fusion/UI/FusionUI/Resources/solid/primitive_pipe",
    "groups": "[FusionSolidCommands]"
  },
  "tooltip": {
    "commandId": "PrimitivePipe",
    "description": "Text: , Description: 選択したパスに沿ってソリッド パイプを作成します。<br><br>パスを選択し、断面とサイズを指定します。<br>, Toolclip: C:/Users/<USERNAME>/AppData/Local/Autodesk/webdeploy/production/1f559bb8ae333199306b5c4f1fe680c6eb7ab9e0/Fusion/UI/FusionUI/Resources/ToolClip/pipe.png"
  },
  "tipsAndTricks": {
    "title": "[パイプ]に関する情報",
    "description": "",
    "moreInfo": "MODEL-PIPE-CMD"
  },
  "inputs": [
    {
      "id": "infoSweepTypeOption",
      "name": "タイプ",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::DropDownValueCommandInput"
    },
    {
      "id": "Profile",
      "name": "プロファイル",
      "tooltip": "",
      "prompt": "スイープするスケッチ プロファイルまたは平面を選択",
      "type": "Nu::Components::ChainSelectionCommandInput"
    },
    {
      "id": "Path",
      "name": "パス",
      "tooltip": "",
      "prompt": "スケッチ曲線またはエッジを選択",
      "type": "Nu::Components::ChainSelectionCommandInput"
    },
    {
      "id": "GuideSurface",
      "name": "ガイド サーフェス",
      "tooltip": "",
      "prompt": "面または作業平面を選択",
      "type": "Nu::Components::ChainSelectionCommandInput"
    },
    {
      "id": "SWEEP_CHAINING_OPTION",
      "name": "チェーン選択",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::BoolValueCommandInput"
    },
    {
      "id": "SWEEP_FULL_PATH",
      "name": "絶対パス",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::BoolValueCommandInput"
    },
    {
      "id": "SWEEP_FLIP_DIRECTION_OPTION",
      "name": "方向を反転",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::BoolValueCommandInput"
    },
    {
      "id": "infoSweepExtentOption",
      "name": "範囲",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::DropDownValueCommandInput"
    },
    {
      "id": "SWEEP_POP_ALONG",
      "name": "距離",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::Components::PointOnPathCommandInput"
    },
    {
      "id": "SWEEP_POP_AGAINST",
      "name": "距離",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::Components::PointOnPathCommandInput"
    },
    {
      "id": "SWEEP_POP_RAIL_DIST_ALONG",
      "name": "ガイド レールの距離",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::Components::PointOnPathCommandInput"
    },
    {
      "id": "SWEEP_POP_RAIL_DIST_AGAINST",
      "name": "ガイド レールの距離",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::Components::PointOnPathCommandInput"
    },
    {
      "id": "SweepTaperAngle",
      "name": "テーパ角度",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::AngleValueCommandInput"
    },
    {
      "id": "SweepTwistAngle",
      "name": "ねじり角度",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::AngleValueCommandInput"
    },
    {
      "id": "infoSweepOrientation",
      "name": "方向",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::DropDownValueCommandInput"
    },
    {
      "id": "infoSweepProfileScalingOption",
      "name": "プロファイル尺度",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::DropDownValueCommandInput"
    },
    {
      "id": "SWEEP_SOFTEN_SHAPE_OPTION",
      "name": "形状をぼかす",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::BoolValueCommandInput"
    },
    {
      "id": "INPUT_SELREORDER",
      "name": "プロファイルを選択",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::SelectionReorderCommandInput"
    },
    {
      "id": "INPUT_RAIL_SEL",
      "name": "パスを選択",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::SelectionReorderCommandInput"
    },
    {
      "id": "INPUT_CENTERLINE_SEL",
      "name": "ガイドを選択",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::SelectionReorderCommandInput"
    },
    {
      "id": "INPUT_GUIDE_SURFACE_SEL",
      "name": "ガイド サーフェスのタイプを選択",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::SelectionReorderCommandInput"
    },
    {
      "id": "infoSectionType",
      "name": "断面",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::DropDownValueCommandInput"
    },
    {
      "id": "SectionRadius",
      "name": "断面サイズ",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::RealValueCommandInput"
    },
    {
      "id": "SectionThickness",
      "name": "断面の厚さ",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::RealValueCommandInput"
    },
    {
      "id": "PIPE_HOLLOW_OPTION",
      "name": "空洞",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::BoolValueCommandInput"
    },
    {
      "id": "infoBooleanType",
      "name": "操作",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::DropDownValueCommandInput"
    },
    {
      "id": "infoCutTableGroup",
      "name": "切り取るオブジェクト",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::CommandInputGroup"
    },
    {
      "id": "infoCutParticipantTable",
      "name": "切り取りに関与するコンポーネントのテーブル",
      "tooltip": "",
      "prompt": "",
      "type": "Nu::TableCommandInput"
    }
  ]
}

パスを選択したり、何かアクションを起こさないと表示されないものも
含めて、全て出力されていると思います。
"name" が画面上に出ている文字で "id" が該当するIDのはずです。

ん~無駄に長くなったぞ。

CATIAをアクティブにする

昔はExcelのマクロからアウトプロセスでCATIAの操作していたんですが、
なんせ遅いんでかなり以前に止めました。

しかし、必要に迫られ作っているものの、昔のものなんてとっくに
捨てていてやっていたことをすっかり忘れている。

Excelからのマクロで処理後、CATIAがアクティブな状態で終わりたいんですが
それも忘れていることの1つ。

探してみたらこんな感じで出来ました。(昔もやっていました)

'excel側です
Private Sub activateCat()

    Dim cat As Object
    Set cat = getCatia()
    
    AppActivate cat.Caption
    
End Sub

Private Function getCatia() As Object

    On Error GoTo GetCatiaErr
        Set getCatia = GetObject(, "CATIA.Application")
    
    Exit Function
GetCatiaErr:
    MsgBox "CATIAが起動していません!", vbExclamation
    End
    
End Function

いつもお世話になっちゃうな Ofice TANAKAさん。

テキストコマンド1

暇を見ては挑戦し続けてきたのですが、ギリギリ何とか書けそうなので
アナウンスしておきたいです。

Fusion360APIでオブジェクトとしては提供されているのに、インスタンスが作成出来ない
ものの代表的なものとしてパイプフューチャーがあります。
Fusion 360 Help
".add" や "create" のようなものが無いため作る事が出来ません。
何故それを許さないのか? は謎なのですが、とにかく作る事が出来ません。

ですが、ちょっと前のUpdateでテキストコマンドがAPIで利用出来るようになったので
最低限レベルで作る事が出来ました。
こんな感じのコードです。

#Fusion360API Python script
#Author-kantoku
#Description-create PipeFeature sample

import adsk.core, adsk.fusion, traceback

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)

def run(context):
    try:
        global _app, _ui
        _app = adsk.core.Application.get()
        _ui = _app.userInterface
        des  :adsk.fusion.Design = _app.activeProduct
        root :adsk.fusion.Component = des.rootComponent

        actSels :adsk.core.Selections = _ui.activeSelections

        # create sketch
        crv = initSktCircle(root)

        # create pipe
        actSels.clear()
        actSels.add(crv)
        initPipe()

        # create sketch
        crv = initSktSpline(root)

        # create pipe
        actSels.clear()
        actSels.add(crv)
        initPipe()

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

def initSktSpline(comp :adsk.fusion.Component):
    skt :adsk.fusion.Sketch = comp.sketches.add(comp.xYConstructionPlane)

    poss = [[-1,2,5], [2,1,0], [0,-4,2]]

    pnt3D = adsk.core.Point3D
    objs = adsk.core.ObjectCollection.create()
    [objs.add(pnt3D.create(x,y,z)) for (x,y,z) in poss]
        
    crvs :adsk.fusion.SketchCurves = skt.sketchCurves
    crv = crvs.sketchFittedSplines.add(objs)

    return crv

def initSktCircle(comp :adsk.fusion.Component):
    skt :adsk.fusion.Sketch = comp.sketches.add(comp.xYConstructionPlane)

    pnt3D = adsk.core.Point3D
    crvs :adsk.fusion.SketchCurves = skt.sketchCurves
    crv = crvs.sketchCircles.addByCenterRadius(pnt3D.create(-5.0,-5,0), 4.0)

    return crv

def initPipe():
    global _app

    txtCmds = [
        u'Commands.Start PrimitivePipe', # show dialog
        u'Commands.SetDouble SWEEP_POP_ALONG 1.0', # input distance
        u'Commands.SetDouble SectionRadius 0.5', # input radius
        u'NuCommands.CommitCmd' # execute command
    ]
    
    for cmd in txtCmds:
        _app.executeTextCommand(cmd)

処理的には、スケッチを描きパイプ作成。再度スケッチを描きパイプを作成しています。
まぁ結果だけを見ると何てことはないんですけどね・・・。

スケッチを描く辺りはどうでも良いのですが、initPipe関数だけちょっとご説明を。
initPipe関数内ではstringのリストを順番に実行しているだけなのですが、
この文字達を見付けるのが非常に大変でした。

Commands.Start PrimitivePip

これはこの辺りで触れている CommandDefinition.execute(以下,cmdDef) に近いもので
GUI時のコマンドのダイアログを表示させています。
f:id:kandennti:20200530141500p:plain
CommandDefinitionsオブジェクト1 - C#ATIA

ところがこちらを記載した時は気が付かなかったのですが、
cmdDefでは、スクリプトの途中で処理させても直ぐには実行してくれず、
スクリプト終了直後に実行されるため、何らかの処理の途中で使用出来ませんでした。
ところが、テキストコマンドの Commands.Start は処理の途中でも即実行してくれます。

又、"PrimitivePip" はパイプコマンドのコマンドIDです。


続いてはこちら。

Commands.SetDouble SWEEP_POP_ALONG 1.0
Commands.SetDouble SectionRadius 0.5

ダイアログが表示された際の "距離" と "断面サイズ" に値を設定しています。
f:id:kandennti:20200530141623p:plain


困難だったのが SetDouble の第一パラメータの "target label" が一体
何を意味するのか? どうやって見付けるのか? が全くの手探りでした。
Fusion360_Small_Tools_for_Developers/TextCommands_txt_Ver2_0_8176.txt at master · kantoku-code/Fusion360_Small_Tools_for_Developers · GitHub
色々と試した結果、ダイアログ上に配置されているcommandInputのIDが
"target label" だとわかりました。
(恥ずかしながら、上手く設定出来た瞬間は声が出てしまいました・・・)

最後はこちら。

NuCommands.CommitCmd

早い話が、こちらの "OK" を押すのと同じ効果があります。
f:id:kandennti:20200530144845p:plain
これも気が付くのに時間がかかりました。APIフォーラムでもこのボタンを
"押す方法が見つからない" との記述を見かけます。

・・・続き書けるかな?

Excelのマクロ2

こちらの続きです。
Excelのマクロ - C#ATIA

続きって程でもないんですけどね。


結局、最初の納期には間に合わず、断念。
次のデータを待っている間に突貫で作りましたが、あまりに
特定の客先向けなので、公開出来ません。(本音は恥ずかしい)

結局作ったのは、ExcelのBookを2つ開き、CATIAのファイルを扱うという
何とも面倒なものとなりました。
CATIA側は大した処理は無いのですが、Excelに苦戦。5:1ぐらいの比率で
手間がかかりました。コード的にはちっともExcelっぽくないはず。

苦しんだ理由の一つが、2つのBookを扱っていたので、「Cells」とか
機能するのですが、「お前、何やつ?」(どっちのBookのやつ?)とか
迷っちゃいます。
CATIAだとDocumentから丹念に下りて行って取得するのですが、いきなり
「Cells」とか「Rows」とかだとビックリしちゃいます。
(人によっては便利と感じるのでしょうが・・・個人的にはスコープのでかい
変数がドカンって置かれているようで不気味です)

もう一つがコレクション。必要なセルを読み取ってかき集め・・・と言うのを
何度か行わなきゃならなかったのですが、後の処理を考えると辞書(ディクショナリ)
が欲しかったんですよ。VBAのコレクションはキー付きでの格納が出来て
単なるコンテナより呼び出しが速かった事を覚えていたので、コレクションを
辞書としてコード書き始めたんですが、コレクションの代入って

Collection.Add value, key

なんですね。 もう違和感しかなかったです。(途中で間違えたし・・・)
確かにKey無しでも使えるのを考えると、key側はオプション引数だから
前にはもっていけないんだろうなぁ とは、変な納得はしました。

後はキーの重複を避ける、isExistのようなものが無いんですね。
「On Error Resume Next」使いながら逃げましたよ。

でも2/3ぐらいまで書いた上で事件が。
コレクションはvalueの列挙は出来るのですが、keyの列挙って出来ないですよね?
ちょっと検索したけど見つかりませんでした。keyの列挙って需要無いこと無いと
思うのですがねぇ。

結局、愕然としながらScripting.Dictionaryに書き換えました、涙流しながら。
2度とコレクションを辞書としては使わない と決めました。



40個ぐらいモジュールがあって、全て「Option Explicit」無しで、スコープの
デカい変数がいっぱいあって、ほとんどの関数がPublicで、定数が全くなくて、
10個弱のクラスモジュールがあって、中身はプロパティぐらいでメソッド
ほぼ無しで、コメント化された古いコードが半分ぐらいあったので、
読み解くモチベーションが出てこなかったです。

出来上がったものは満足出来ていないので、最低限にすらちょっと足りない
レベルですが、多少の変更にもコードを修正しないでシートの値を修正する
だけで対応出来るようにしたし、オリジナルより処理が速いはず。

Excelのマクロ

急遽、作らなきゃならなくなった。
困った、納期迫っているのに・・・。

元のソース見てもわからない。あぁ、世間の皆様が感じるアレだと痛感。

みんなPublicな関数で何処に書いてあるのか・・・。
せめてモジュール名.関数名で記載していて欲しい。
スコープのデカい変数だらけ。
リファクタリングって言えばそうかもしれないけど、異なる処理の
類似した関数名、判断が苦しい。
コメント化されている、古いコードが大量にある。

あぁ、世間の皆様が感じるアレだと痛感。

やりたくない・・・。でも最低限動くようにしないと逆に間に合わない。