C#ATIA

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

コマンドのログ取得

Fusion360のイベント処理が少しづつ理解出来るようになってきた・・・つもり。

前からCADを操作したログが、何処かにあるのではないかな?と思ってはいる
のですが、未だに見つからず。
逆にイベントを利用すると取得出来ることに気が付き、今後の事もあってログを
取得するためのアドインを作りました。

#Fusion360 python adinn
#Author-kantoku
#Description-command logging

import adsk.core, adsk.fusion, adsk.cam, traceback

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

class CommandHandler(adsk.core.ApplicationCommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            data = {
                'act_cmd':'ui.activeCommand',
                'args_cmd_id':'args.commandId'
            }

            ui  = _app.userInterface
            cmdlog = []
            for key in data:
                res = None
                try:
                    res = eval(data[key])
                except:
                    res = '(null)'
                cmdlog.append(key + '||{:35}'.format(res))
            print(' - '.join(cmdlog))

        except:
            print('errer!!')

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

        onCommand = CommandHandler()
        ui.commandTerminated.add(onCommand)
        _handlers.append(onCommand)
        print('--- Start addin ---')

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

def stop(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        for hdl in _handlers:
            ui.commandTerminated.remove(hdl)
        
        _handlers.clear()
        print('--- Stop addin ---')

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

ログ取得なんて大げさに書きましたが、デバッグコンソールに垂れ流しているだけです。
f:id:kandennti:20200123125741p:plain
”これが何に役立つの?” と思うかもしれませんが、分かっていないです。
但し、他人には間違いなく役に立たないです。

ちょっと欲しいものと違うんだよなぁ・・・。

PreSelectイベントサンプル

こちらに記載した内容とちょっと関連してます。恐らく。
解決済み: Re: ValidateInputsEventHandlerのイベントによる呼び出しについて - Autodesk Community

本家のAPIフォーラムで質問した際のものは、
f:id:kandennti:20200122115241p:plain
SelectionCommandInput(要素を選択するやつ)が2個有り、希望としては
”2個共何かが選択された際にOKボタンが機能するようにしたい”
と言うことです。
何か処理する際に片方だけでは処理出来ないので、OKボタンは
押せない状態にしておくのが自然な事ですよね?
でもAPIフォーラムに記載した最初のコードでは実現できず、
さらに2個目でうまく行くと思ったのにそれでもNGだったのです。

教えて頂いたのは、SelectionCommandInputの選択制限数を
無制限にすれば機能するよ って事でした。
Fusion 360 Help

・・・
            i1 = inputs.addSelectionInput(_input1ID, _input1ID,  _input1ID)
            #i1.setSelectionLimits(1,1) #NG
            i1.setSelectionLimits(0) #OKだけど無制限に選択出来ちゃう
・・・

仮に、各SelectionCommandInputで1個づつでなければ処理出来ない場合は、
当然、選択数が無制限では困っちゃう場合があるので、他の何らかの方法で
防ぐ必要に迫られる事になります。

それを考えるとsetSelectionLimitsメソッド(プロパティじゃ無いんだな…)は、
1個の時のみ簡単に実装するための機能なんだなぁ と感じます。

つまり、そこまで拘るなら お前が頑張れ! って受け止めました。



と、ここまでが前置きです。
選択を防ぐ方法としては幾つか有りそうなのですが、恐らくCommandオブジェクトの
preSelectイベントが良さそうです。
Fusion 360 Help
・・・Helpの説明がやたらと長い。

記載によれば、2個を選択出来るようにして平面を選択フィルタとし、
お互いが並行でなければならなければ と言う条件の場合は
Fusion 360 Help
こちらだけでは不足してます。
OKボタンを押した後に、 "お前が選択した平面は平行じゃないから、中止するね" と
メッセージを出すのは、表示させる言葉をいくら丁寧にしても不親切。

preSelectイベントは選択直前の処理なのですが、マウスカーソルの下の面等の取得が
可能です。選択させたくない場合は、、、、Helpの説明がやたらと長い割には
そこの説明が抜け落ちてる。

class PreSelectHandler(adsk.core.SelectionEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        eventArgs = adsk.core.SelectionEventArgs.cast(args)

こんな感じのイベントハンドラを作った場合、argsはSelectionEventArgsオブジェクトなので、
Fusion 360 Help

        eventArgs.isSelectable = False

こんな感じにすると選択出来なくなります。これは対象となるオブジェクトを
選択不可にするわけじゃなく、一時的な事の様です。
f:id:kandennti:20200122115423p:plain
なるほど、是非ともHelpに記載して欲しい。

と言うことでサンプルです。

#Fusion360API Python script
#Author-kantoku
#Description-PreSelectイベントサンプル

import adsk.core, adsk.fusion, traceback

_commandId = 'PreSelectionEvent'
_SelIpt1ID = 'Selectioninput1'

_handlers = []
_app = None
_ui = None

class CommandDestroyHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            adsk.terminate()
        except:
            if _ui:
                _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

class ValidateInputHandler(adsk.core.ValidateInputsEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            eventArgs = adsk.core.ValidateInputsEventArgs.cast(args)

            inputs = eventArgs.inputs
            i1 :adsk.core.SelectionCommandInput = inputs.itemById(_SelIpt1ID)

            # 選択が2個ならOKボタンを有効に
            if i1.selectionCount == 2:
                eventArgs.areInputsValid = True
            else:
                eventArgs.areInputsValid = False
                
        except:
            if _ui:
                _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

class PreSelectHandler(adsk.core.SelectionEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:
            eventArgs = adsk.core.SelectionEventArgs.cast(args)

            i1 :adsk.core.SelectionCommandInput = eventArgs.firingEvent.activeInput
            if i1.selectionCount == 2:
                # 既に選択が2個なら許さない
                eventArgs.isSelectable = False
                return
                
            if i1.selectionCount < 1 :
                # 選択が0個なら問題なし
                return

            selGeo :adsk.core.Plane = i1.selection(0).entity.geometry
            preGeo :adsk.core.Plane = eventArgs.selection.entity.geometry

            # お互いが平面か確認して判断
            eventArgs.isSelectable = selGeo.isParallelToPlane(preGeo)

        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:
            cmd = args.command

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

            onValidateInput = ValidateInputHandler()
            cmd.validateInputs.add(onValidateInput)
            _handlers.append(onValidateInput)

            onPreSelect = PreSelectHandler()
            cmd.preSelect.add(onPreSelect)
            _handlers.append(onPreSelect)

            inputs = cmd.commandInputs
            i1 = inputs.addSelectionInput(_SelIpt1ID, _SelIpt1ID,  _SelIpt1ID)
            i1.setSelectionLimits(0)
            # 平面のみ
            filters = ['PlanarFaces','ConstructionPlanes']
            [i1.addSelectionFilter(s) for s in filters]

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

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

        cmdDefs = _ui.commandDefinitions

        cmdDef = cmdDefs.itemById(_commandId)
        if cmdDef:
            cmdDef.deleteMe()

        cmdDef = cmdDefs.addButtonDefinition(_commandId, _commandId, _commandId)

        onCmdCreated = CommandCreatedHandler()
        cmdDef.commandCreated.add(onCmdCreated)
        _handlers.append(onCmdCreated)
        cmdDef.execute()

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

こんな感じで動作します。選択数とOKボタンを見て頂きたいです。
(マウスカーソルが停止しているときは、クリックしまくってます)

対向ピストンエンジン

あぁリンク消えちゃうかな?
2025年F1パワーユニットは2ストローク化の噂。“うるさくて臭い”エンジンがF1に乗りうるワケ(オートスポーツweb) - Yahoo!ニュース

こんなエンジン初めて知りました。楽しそうじゃない。

2st批判は出るでしょう、きっと。 でも、あれだけ(国内では・・今でもかな?)
イメージの悪かったディーゼルエンジンだって静かになった
(排気ガスは綺麗になったとは思ってないです…)ぐらいなんだから、
きっと4stと遜色無いレベルにまでなるんじゃないかな?

近年2回/年 開催予定のアメリカGPは出来なくなるのかな?

カスタムグラフィックのサンプル

カスタムグラフィックのサンプルを作ってみました。

スクリプト実行後ボディをクリックすると、ボディが破壊されます・・・。
実際のボディは非表示にしているだけなので、問題無いです。

#Fusion360API Python script
#Author-kantoku
#Description-カスタムグラフィックスサンプル

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
        des  :adsk.fusion.Design = app.activeProduct
        root :adsk.fusion.Component = des.rootComponent

        msg :str = 'Select Body'
        selFiltter :str = 'Bodies'
        sel :adsk.core.Selection = selectEnt(ui, msg ,selFiltter)
        if not sel: return
        body :adsk.fusion.BRepBody = sel.entity
        
        adsk.fusion.BRepFace.getCG = getCG
        adsk.fusion.BRepFace.getMat = getMat
        cgs :adsk.fusion.CustomGraphicsGroups = root.customGraphicsGroups
        cg_mat = [(f.getCG(cgs), f.getMat()) for f in body.faces]

        body.isLightBulbOn = False
        ui.activeSelections.clear()
        for i in range(10):
            for cg, mats in cg_mat:
                cg.transform = mats[i]
            app.activeViewport.refresh()
            adsk.doEvents()

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

def getCG(
    self :adsk.fusion.BRepFace,
    cgs :adsk.fusion.CustomGraphicsGroups):

    cg = cgs.add()
    mesh = self.meshManager.displayMeshes.bestMesh
    cgCoords = adsk.fusion.CustomGraphicsCoordinates
    coords = cgCoords.create(mesh.nodeCoordinatesAsDouble)
    cgMesh = cg.addMesh(coords, mesh.nodeIndices, 
        mesh.normalVectorsAsDouble, mesh.nodeIndices)
    return cgMesh


def getMat(
    self :adsk.fusion.BRepFace):

    body :adsk.fusion.BRepBody = self.body
    cog :adsk.core.point3D = body.physicalProperties.centerOfMass
    vec :adsk.core.Vector3D = cog.vectorTo(self.pointOnFace)
    vec.normalize()
    move = adsk.core.Vector3D.create()
    mat :adsk.core.Matrix3D = adsk.core.Matrix3D.create()
    mats = []
    for i in range(10):
        move.add(vec)
        mat.translation = move
        mats.append(mat.copy())
    return mats

def selectEnt(
        ui :adsk.core.UserInterface,
        msg :str, 
        filtterStr :str) -> adsk.core.Selection:

    try:
        sel = ui.selectEntity(msg, filtterStr)
        return sel
    except:
        return None

正直なところ想像していたより、処理が重かったです。
それ以上に思ったより面白くない。


今回動画を、こちらで紹介しているサイトのサービスを利用してみました。
はてなブログにMP4動画を超簡単にアップロード投稿する方法を紹介します - イーサモンとディセントラランドで一緒に歩くんだZ!
確かにお手軽です。

カスタムグラフィックスを削除する2

Fusion360APIでは、画面上に面等の要素を表示させる方法が
知っている限りでは3つあります。

・BRep
通常の手動(GUI)で作業した際に出来上がるものです。

・TemporaryBRep
結果的にBRepと同じになりますが、履歴が使えません。
又、複雑な形状を作る方法が恐らく無く、プリミティブなもののみです。
但し、カーネルで直接処理されるようで高速です。

・Custom Graphics
あくまで画面上に要素を表示されるのみで、モデリングには利用出来ないものです。
主にコマンドのプレビューに利用する為のものの様です。

Custom GraphicsがAPIで公開されたのは、TemporaryBRepよりも早いです。
恐らく処理は3つの中では最速なのでは無いかと思ってます。
但し、ちょっとフォーマットが独特で扱いにくいです。


試したことが無いため取り組みたいのですが、Custom Graphicsは手動で
削除出来ません。質が悪い・・・。

そこでまずは、ファイル内の全てのCustom Graphicsを削除するだけのスクリプト
作成しました。

#Fusion360API Python script
#Author-kantoku
#Description-カスタムグラフィックス全てを削除する

import adsk.core, adsk.fusion, adsk.cam, traceback

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

        # カスタムグラフィック取得
        cgs = [cmp.customGraphicsGroups for cmp in des.allComponents]
        cgs = [cg for cg in cgs if cg.count > 0]

        msg :str = ''
        if len(cgs) < 1:
            msg = '現在のファイルにはカスタムグラフィックがありませんでした'
            ui.messageBox(msg)
            return

        # 問い合わせ
        msg = '現在のファイルには消去されていないカスタムグラフィックが存在します。\n'
        msg += '全て消去しますか?'
        if not userQuery(ui, msg):
            return

        # 削除
        for cg in cgs:
            gps = [c for c in cg]
            gps.reverse()
            for gp in gps:
                gp.deleteMe()

        # 終了
        app.activeViewport.refresh()
        ui.messageBox('done')
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

def userQuery(
    ui :adsk.core.UserInterface,
    msg :str) -> bool:

    btnTypes = adsk.core.MessageBoxButtonTypes
    icnTypes = adsk.core.MessageBoxIconTypes
    dlgRes = adsk.core.DialogResults
    res = ui.messageBox(msg, '', btnTypes.OKCancelButtonType, icnTypes.QuestionIconType)
    if res == dlgRes.DialogOK:
        return True
    return False

※全てを削除していなかったバグを修正しました・・・。

pythonのflort型のrange関数

fusion360APIをやっているといつも欲しくなる。
その都度検索して探し出してくるので覚書。

こちらを拝借し、ちょっとだけ好みに変えました。
Pythonのrange関数をfloat型に拡張してみた - S0-ma's Blog

def dRange(start, end, step):
    from decimal import Decimal

    start = Decimal(str(start))
    end = Decimal(str(end))
    step = Decimal(str(step))

    for i in range(int((end - start) / step)):
        yield float(start + i * step)

標準ライブラリに入ってくれないかなぁ・・・。

パンチタップ

以前、加工屋さんのブログ(現在は無くなりました)で紹介されていたタップを
思い出しました。

エムーゲ・フランケンのパンチタップと言うものです。こちらの動画を
紹介していました。
www.youtube.com

最初はどのような仕組みなのか全く理解出来ませんでした。(説明されているのに…)


こちらの動画は初めて見ました。
www.youtube.com

下穴ドリルと変わらない程の時間で、タップを加工しちゃってます。

すごいなぁ、買わないけど。