C#ATIA

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

俺コマンドを作る1

Fusion360のアドインを何個か作りましたが、実はツールバーに登録・発火させる
仕組みが分かっていないです。
今までこちらを使って、楽をしていたものでして・・・。
github.com

あちら、非常に便利なのですが対応しているイベントが少なく、作りたいものが未対応
状態なもので、重い腰を上げ勉強してみました。

f:id:kandennti:20200206183141p:plain
こんな感じにツールバーに登録するのが目的です。
・・・英語で "MyCommand" だったら、"俺コマンド" と言う表現で間違いありません。

時間が無いので、とりあえずコードだけ記載。中身は後日です。
念のためお伝えしておくと、この "俺コマンド” は何もしません。
何もしないのに、このボリュームは相当ハードルが高いです。
スクリプトの方が開発が桁違いに楽なんです。

#FusionAPI_python addin
#Author-kantoku
#Description-俺コマンド(アドイン)を作成するサンプル

import adsk.core, adsk.fusion, traceback

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

# 俺コマンド用情報
_cmdInfo = ['cmdTest','俺コマンド','偉そうにしているが何も出来ない']

# SelectionCommandInput用情報
_selInfo = ['dlgSel','さぁ選べ!!','何もしないから']

# 俺コマンドを追加するタブのID
_tabId = 'SolidTab'

# 俺コマンドを追加するパネルのID
_panelId = 'InspectPanel'


def run(context):
    ui = None
    try:
        # 色々と使う、ApplicationとuserInterfaceを取得
        global _app, _ui
        _app = adsk.core.Application.get()
        _ui = _app.userInterface

        # -- 俺コマンドを作成するためにcommandDefinitionを作るよ --

        # 今利用出来る全てのコマンドを取得
        cmdDefs :adsk.core.CommandDefinitions = _ui.commandDefinitions

        # 既に同一のIDの俺コマンドが無いか? 確認してみる
        global _cmdInfo
        cmdDef :adsk.core.CommandDefinition = cmdDefs.itemById(_cmdInfo[0])
        if cmdDef:
            # 見つけたら非情にも消し去る(行儀悪いから良い子はしない)
            cmdDef.deleteMe()

        # commandDefinitionを作る
        # http://help.autodesk.com/view/fusion360/ENU/?guid=GUID-04eb6198-72cd-4430-a6a4-8d68a1105b8e
        cmdDef = cmdDefs.addButtonDefinition(_cmdInfo[0],_cmdInfo[1],_cmdInfo[2])

        # 俺コマンドはイベントを使って産み出す
        # もうおまじないレベル
        # http://help.autodesk.com/view/fusion360/ENU/?guid=GUID-895ee146-8697-4ba0-98e7-4f72b74edb4f
        global _handlers
        onCommandCreated = CommandCreatedHandler()
        cmdDef.commandCreated.add(onCommandCreated)
        _handlers.append(onCommandCreated)


        # -- 俺コマンドを呼び出しやすくする為にパネルに登録するから --

        # タブを使用しているか確認
        # 旧UIの関係かも 恐らくもういらないと思う
        if not _ui.isTabbedToolbarUI: return

        # 'デザイン' の全タブを取得
        desTabs = adsk.core.ToolbarTabs.cast(None)
        desTabs = _ui.toolbarTabsByProductType('DesignProductType')
        if desTabs.count < 1: return

        # 目的のタブ取得
        global _tabId
        targetTab :adsk.core.ToolbarTab = desTabs.itemById(_tabId)

        # タブ見つかった?
        if not targetTab:
            # 見つからないので作っちゃう
            # https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-81247FED-3EC7-4EEA-80DD-0017F0E2450E
            # targetTab = desTabs.add(_tabId, '俺タブ')
            # と思ったけど未対応っぽい(ver2.0.7421)
            _ui.messageBox('未対応タブID!')

        # 目的のタブ内の全パネル取得
        panels :adsk.core.ToolbarPanels = targetTab.toolbarPanels
        if panels.count < 1: return

        # 目的のパネル取得
        global _panelId
        targetpanel :adsk.core.ToolbarPanel = panels.itemById(_panelId)

        # パネル見つかった?
        if not targetpanel:
            # 見つからないので作っちゃう
            # https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-6b387530-93c1-4238-aefb-eca54b03852d
            targetpanel = panels.add(_panelId, '俺パネル')

        # パネルを使えるように
        #Ver2.0.9144以降、読み込み専用プロパティの為、この指定はエラーになる
        # targetpanel.isVisible = True

        # パネル内のコントロール取得
        # あれはコントロールって呼ぶんだな
        controls :adsk.core.ToolbarControls = targetpanel.controls

        # 既に 俺コマンドが登録されてる?
        cmdControl :adsk.core.ToolbarControl = controls.itemById(cmdDef.id)
        if cmdControl:
            # 見つけたら非情にも、また消し去る
            cmdControl.deleteMe()

        # 俺コマンドをパネルに登録
        # https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-1ed0cfb5-2ad4-4285-9eec-484ef19f2729
        cmdControl = controls.addCommand(cmdDef)

        # 俺コマンドを使える状態にするぞ
        # あくまで登録しているだけで、コントロールが押されるまで待機
        cmdControl.isVisible = True
        cmdControl.isPromoted = True
        cmdControl.isPromotedByDefault = True                

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

# 俺コマンド作成
class CommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            global _ui, _handlers
            cmd = adsk.core.Command.cast(args.command)
            
            # 本当はここにコマンドのイベント類を記述する

            # SelectionCommandInputを追加
            global _selInfo
            inputs = cmd.commandInputs
            inputs.addSelectionInput(_selInfo[0], _selInfo[1], _selInfo[2])
        except:
            if _ui:
                _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

# 俺コマンド削除
def stop(context):
    try:
        # 全てのパネルを取得
        panels :adsk.core.ToolbarPanels = _ui.allToolbarPanels

        # 俺コマンドの入ったパネル取得
        global _panelId
        panel :adsk.core.ToolbarPanel = panels.itemById(_panelId)

        # 行儀良く、俺コマンドを削除
        global _cmdInfo
        if panel:
            panel.controls.itemById(_cmdInfo[0]).deleteMe()

        # タブ作っていたら削除
        # と思ったけど現状はタブ自体が作れない(ver2.0.7421)
        global _tabId
        tabs :adsk.core.ToolbarTabs = _ui.allToolbarTabs
        tab :adsk.core.ToolbarTab = tabs.itemById(_tabId)
        if not tab:
            if tab.toolbarPanels.count < 1 and not tab.isNative:
                tab.deleteMe()

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

なるべくコメントやType Hints、使わなくても良い部分でもglobalを付けました。
個人的にはもっとリファクタリングしたいのですが、逆に "わかりにくい" と
言われることがあったため、あえてエントリポイント(run)にゴリゴリと書いてます。

元ネタはこちらです。
Fusion 360 Help
ネストが深いの嫌なんだよね。

APIでリンク付きインポート

こちらに記載した事をちょっとだけテストしてみました。
解決済み: 思い付いた事をウダウダと - Autodesk Community

結果だけ書くと
・「adsk.doEvents()」使うと、黒くなるのは回避出来る。
・オフラインにした時点でDataHubにアクセス出来ない



ついでに、50個ぐらいのデータのリンク付きインポートを試してみました。
(50個コピー作るのもAPIでやりましたが、すごく早い)
こんな感じの手抜きコードです。

#Fusion360API Python script

import adsk.core, adsk.fusion, traceback

_app = adsk.core.Application.cast(None)
_ui  = adsk.core.UserInterface.cast(None)
import time
def run(context):
    try:
        # 目的のフォルダのIDが分かっている事前提
        target_folder_id = 'urn:adsk.wipprod:fs.folder:co.Mh12739LTn6Rp0pWqJgHmQ'
        new_filename = 'piyo'

        # オマジナイ
        global _app, _ui
        _app = adsk.core.Application.get()
        _ui = _app.userInterface

        t = time.time() 

        # アクティブプロジェクト取得
        project = _app.data.activeProject

        # 目的のフォルダ取得
        fols =  project.rootFolder.dataFolders
        children_folder = fols.itemById(target_folder_id)

        # 新規ファイル作成
        # 恐らく目的のファイルと同一プロジェクトに保存されていることが前提の様
        newDoc = _app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)
        newDoc.saveAs(new_filename, project.rootFolder, 'これはAPIテスト用です', '') 
        root :adsk.fusion.Component = _app.activeProduct.rootComponent

        # リンク付き取り込み
        mat = adsk.core.Matrix3D.create()
        for file in children_folder.dataFiles:
            root.occurrences.addByInsert(file,mat,True)
            adsk.doEvents()
        adsk.doEvents()

        elapsed_time = time.time()-t
        print(f"time:{elapsed_time}")
    except:
        if _ui:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

あ、今日まで画像が貼れない・・・。
結果は

time:181.88442206382751

1個3.5秒ぐらい、遅いですね。

全ての adsk.doEvents() を止めると

time:178.5801386833191

ほぼ変わらないです。タスクマネージャのCPUを見ててもFusion360は2~3%
ぐらいしか動いていないので、遅いのはサーバー側です。恐らく。

3.5x800=2800

すんなり処理出来ても、50分弱。 恐らく、処理数と時間との関係は
2次関数っぽく増えるのだろうと思うと、2~3時間ぐらいかかるんじゃないかな?
(単純なbox1個だけのデータ)

手作業よりは早いかもしれないけど。

クイックアクセスツールバー

迂闊だった。画像の1か月でのアップロード容量制限に引っかかってしまい
画像無しで行けるだろうか?(6年目だけど初めて・・・)


ちょっと古い記述なのですが、こちら。
Solved: MORE TASK BAR USER DEFINED FIELDS - Autodesk Community

これ、僕も昨年サポートに問い合わせたんです。

Help画像の⑦のところですが、現在選択している工具径・回転数等を任意の情報を
カスタマイズして表示させられます。
Help

バージョンアップする度にダイアログの表示が遅くなってきたので、
手っ取り早く確認するのに便利なのですが、最大4個のみなんです。

「もっと表示させたい」→「要望として出しておきます」

で終わってしまったのですが、まさかクイックアクセスツールバー
表示させるとは・・・。

確かに①の位置に表示させると事が出来ます。ボタンのみが追加出来る
と思っていた先入観があったので、思い付きませんでしたよ。
恐るべし、発想の転換



ブログもProに切り替えると言う選択肢も無くも無いのですが、
そこまでする程の内容でも無いですし、ノルマ感を背負うと
すぐ止めちゃいそうなので、とりあえずこのままで行きたい。

Camera.viewExtents

こちらにちょっとレスしました。
Manipulating the camera view - Autodesk Community


手動で「ウィンドウ内を検索」した際と、APIの Viewport.fit() 又は
Camera.isFitView = True した際で、ズームされた状態が違う ってお話です。
CATIAのリフレームオンと同等の機能です。

投稿者さんの記載では、APIで行うと近すぎるらしいです。
・・・細かい。


カメラ絡みは少し経験があったので、こちらの存在を知っていました。
Fusion 360 Help
画面で見える範囲の球体の半径をプロパティとして持っています。

ちょっとだけで大きくすれば、「ウィンドウ内を検索」と同等の事が
出来るだろうと思い、あちらのサンプルコードを作りました。



まさか、被せるようにブログに説明を記載するとは。
Mod the Machine: Working with Cameras (Part 1)


内容はInventorのマクロですが、Fusion360も同じような仕様らしいです。
確かにコードを見ると、オブジェクトの仕様は類似している感じ。
カーネルが同じだから、そうなるでしょう)

Fusion360はアニメーション作業スペースがあるから、APIで動画を作る必要が
無いと思うんだけどなぁ。

Fusion360 ver2.0.7402 API デバッグ調子悪い

こちらに記載したのですが、調子悪いんです。
socket.timeout: timed out - Autodesk Community

昨日もアドインを作成していても、とにかく調子悪い。
こちらのコードも自信無いだけに、原因がわからず。

ver2.0.7402になってからは、多くのアドインが機能しなくなっている
状況の様です。 2/3のUpdateまで待て! との事。
Betreff: My custom ToolbarControls are disabled in MANUFACTURE :( - Autodesk Community



ついでに、英語が理解出来ず勘違いして作ったスクリプト
f:id:kandennti:20200128130933p:plain
ここのホーム(デフォルト状態)を押した際のビューの位置をAPI再現。

#Fusion360API Python script

import adsk.core, adsk.fusion, traceback

def run(context):
    ui = None
    try:
        app  :adsk.core.Application = adsk.core.Application.get()
        ui   :adsk.core.UserInterface = app.userInterface

        execHomeViewLike()

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

def execHomeViewLike():
    app :adsk.core.Application = adsk.core.Application.get()
    pref_Up = app.preferences.generalPreferences.defaultModelingOrientation

    pnt3d = adsk.core.Point3D
    vec3d = adsk.core.Vector3D
    Z_UP = adsk.core.DefaultModelingOrientations.ZUpModelingOrientation

    vp = app.activeViewport
    vp.isSmoothTransition = True
    
    cam = vp.camera
    cam.target = pnt3d.create(0,0,0)    
    if pref_Up == Z_UP:
        cam.eye = pnt3d.create(1,-1,1)
        cam.upVector = vec3d.create(0,0,1)
    else:
        cam.eye = pnt3d.create(1,1,1)
        cam.upVector = vec3d.create(0,1,0)

    vp.camera = cam
    vp.fit()

全くと言って良いほど、使い道が分からず・・・。

こちらのCamera.viewOrientationプロパティを使うと出来るのかな?
と思って試したのですが、現状のキューブの向きに対して右やら左やら
が決まるようで、意図しない向きになることがシバシバ。

Electronics Package Generator

先日、Fusion360のUpdateがありました。恐らくその際にこれが
追加されたのだろうと思います。

f:id:kandennti:20200125231753p:plain

PCB機能絡みのものでは無かろうか?
将来的にはPCBもスプリクト/アドインが作れるのかな?

Selection Filtersの不具合に挑む

先日、PreSelectイベントの存在に気が付きました。
PreSelectイベントサンプル - C#ATIA


個人的には "選択する直前に発生するイベント" と言うより
"選択可能な要素の上にマウスカーソルが乗った際に発生するイベント"
と思っています。


古い記載ですが、こちらの不具合をテストしたことがあります。
Select SketchConstraints using api - Autodesk Community
まぁ正直なところ、スケッチの拘束のみを選択するスクリプト
何に役立つのか想像出来ないのですが、確かに不具合の様で
エラーとはならないものの、選択が出来ません。

先日のPreSelectイベントを利用することで、この問題を解決出来るだろうを
思いました。

長いので、こちらにupしています。
GitHub - kantoku-code/Fusion360-SelectFilter_T_Sample: Fusion360API Python script


PreSelectイベントのargsを利用すると、選択せずに要素の取得が出来ます。

hoge = args.selection.entity

この型をチェックすることで、フィルターとしての役割をさせます。

生憎、問題となる "SketchConstraints" と言う型は無いので、それっぽい

'SketchAngularDimension'
'SketchConcentricCircleDimension'
'SketchDiameterDimension'
'SketchLinearDimension'
'SketchEllipseMajorRadiusDimension'
'SketchEllipseMinorRadiusDimension'
'SketchOffsetDimension'
'SketchRadialDimension'

であることをチェックします。今思うと、'Sketch'と'Dimension'を
正規表現でチェックすればカッコよかったなぁ・・・。

と、ここまでは何の問題も無く思い付きました。
問題は、インスタンスのSelectionCommandInput毎にこのフィルターを
持たせてやりたいんです。

よくわかっていないのですが、プロパティを動的に付加すれば良いじゃん
と思いこんな感じで行いました。

selSktCon = inputs.addSelectionInput(hoge,piyo,huga)
selSktCon.filterT = 'SketchAngularDimension'

これ駄目っぽいんです。メソッドは許されるのにプロパティは駄目なんですね。
正しくは、設定出来るのですがフィルタリングするためにPreSelectイベントで
呼び出すと消えちゃいます・・・。

adsk.core.SelectionCommandInput.filterT = 'SketchAngularDimension'

これは許してくれるのですが、これではクラス変数となり各インスタンス
では無いため、無意味なんです。


続いてメタプログラミング的な方法なこちらのsetattr,getattrを使ったものの
setattr属性の追加 - Python学習講座
上記と同様、フィルタリング時には使えない。


続いて思い付いたのが、Fusion361APIのオブジェクトには、属性(attribute)を
持たせる事が可能なものがあります。例えば面要素のBRepFace何かそうです。
Fusion 360 Help
(記憶だと、この情報もファイルに保存されるはずです)
でも、SelectionCommandInputにはattributesプロパティが無いんです・・・。


結局、フィルタを
{SelectionCommandInput : list[フィルタとなるTYPE]}
の形のスコープのデカい辞書を用意し、やり取りするための
拡張メソッドを諸々用意してみました。
スコープのデカいので、メソッド経由では無くても書き換えられちゃうん
ですけどね。もっと良い方法が有りそうな気がする。


フォーラムに僕が記載した "SphericalFaces" フィルタもこんな感じの
方法で解決まではたどり着けるけど、もっと奥深くしなきゃならないので
割愛しておきます。