こちらの続きです。
ダイアログなスクリプト入門4 - C#ATIA
前回は、executeとexecutePreviewイベントを取り扱いました。相変わらず役立つ
物ではありませんが、目の前に結果が表示されるようになると ”やっている” 感は
感じると思います。
今回は少し地味目なテーマですが、より細かな選択のフィルターとOKボタンの制御
を扱いたいと思います。
今回のゴール
見た目は前回と同じSelectionCommandInputが1個だけです。
あまり現実的ではありませんが、今回のフィルターとしての条件は
・スケッチの点でY座標がマイナスの場合、選択させない。
又、OKボタンを利用出来るようにするためには
・3点以上の点が選択され、1個以上はX座標がプラスの必要がある。
とします。無理やりですね・・・。
ベースとなるコード
こちらは前回の最終的なものとほぼ同じです。
但し、SelectionCommandInput.setSelectionLimitsメソッドを設定して
おりません。
# Fusion360API Python script import traceback import adsk.fusion import adsk.core import time _app: adsk.core.Application = None _ui: adsk.core.UserInterface = None _handlers = [] def run(context): try: global _app, _ui _app = adsk.core.Application.get() _ui = _app.userInterface cmdDef: adsk.core.CommandDefinition = _ui.commandDefinitions.itemById( 'test_cmd_id' ) if not cmdDef: cmdDef = _ui.commandDefinitions.addButtonDefinition( 'test_cmd_id', 'ダイアログです', 'ツールチップです' ) 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())) class MyCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): def __init__(self): super().__init__() def notify(self, args: adsk.core.CommandCreatedEventArgs): adsk.core.Application.get().log(args.firingEvent.name) try: global _handlers cmd: adsk.core.Command = adsk.core.Command.cast(args.command) # event onDestroy = MyCommandDestroyHandler() cmd.destroy.add(onDestroy) _handlers.append(onDestroy) onExecutePreview = MyExecutePreviewHandler() cmd.executePreview.add(onExecutePreview) _handlers.append(onExecutePreview) # inputs inputs: adsk.core.CommandInputs = cmd.commandInputs selIpt: adsk.core.SelectionCommandInput = inputs.addSelectionInput( 'selIpt', '点を選択', 'スケッチの点を選択して下さい' ) selIpt.addSelectionFilter('SketchPoints') except: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) class MyExecutePreviewHandler(adsk.core.CommandEventHandler): def __init__(self): super().__init__() def notify(self, args: adsk.core.CommandEventArgs): adsk.core.Application.get().log(args.firingEvent.name) selIpt: adsk.core.SelectionCommandInput = args.command.commandInputs.itemById( 'selIpt') pnts = [selIpt.selection(idx).entity.worldGeometry for idx in range( selIpt.selectionCount)] createSphereBodies(pnts) args.isValidResult = True def createSphereBodies(pnts: list): radius = 1 tmpMgr: adsk.fusion.TemporaryBRepManager = adsk.fusion.TemporaryBRepManager.get() sphereBodies: list = [] pnt: adsk.core.Point3D for pnt in pnts: sphere: adsk.fusion.BRepBody = tmpMgr.createSphere(pnt, radius) sphereBodies.append(sphere) app: adsk.core.Application = adsk.core.Application.get() des: adsk.fusion.Design = app.activeProduct root: adsk.fusion.Component = des.rootComponent isParametric: bool = True if des.designType == adsk.fusion.DesignTypes.DirectDesignType: isParametric = False occ: adsk.fusion.Occurrence = root.occurrences.addNewComponent( adsk.core.Matrix3D.create() ) comp: adsk.fusion.Component = occ.component baseFeat: adsk.fusion.BaseFeature = None if isParametric: baseFeat = comp.features.baseFeatures.add() bodies: adsk.fusion.BRepBodies = comp.bRepBodies if isParametric: baseFeat.startEdit() for body in sphereBodies: bodies.add(body, baseFeat) baseFeat.finishEdit() else: for body in sphereBodies: bodies.add(body) class MyCommandDestroyHandler(adsk.core.CommandEventHandler): def __init__(self): super().__init__() def notify(self, args: adsk.core.CommandEventArgs): adsk.core.Application.get().log(args.firingEvent.name) adsk.terminate()
より細かなフィルターを作る
選択フィルターについては以前こちらで扱いました。
ダイアログなスクリプト入門2 - C#ATIA
フィルターについては大半は用意されているもので事が足りますが、
より細かな条件のものが必要な場合になる事もあると思います。
それを実現するのが事前選択になります。
事前選択を別の表現をすれば、選択可能な要素の上にマウスカーソルを持って
行った際、マウスカーソル下の要素を取得する事が可能です。
Fusion360APIでは2種類用意されており、"preSelect" と "preSelectMouseMove" が
有ります。 ・・・個人的にドキュメントの説明からするとイベントの発火が
逆のような気がしてます。
どちらでも同じですが、今回はpreSelectイベントを利用する事にしましょう。
まず、CommandCreatedイベントハンドラー内に追加します。
イベントハンドラーの登録はCommandCreatedイベントハンドラー内だと思って
おいて大丈夫です。
・・・ onExecutePreview = MyExecutePreviewHandler() cmd.executePreview.add(onExecutePreview) _handlers.append(onExecutePreview) # preSelectイベントハンドラー登録 onPreSelect = MyPreSelectHandler() cmd.preSelect.add(onPreSelect) _handlers.append(onPreSelect) # inputs inputs: adsk.core.CommandInputs = cmd.commandInputs ・・・
続いてハンドラーを作成します。
# 事前選択です! class MyPreSelectHandler(adsk.core.SelectionEventHandler): def __init__(self): super().__init__() def notify(self, args: adsk.core.SelectionEventArgs): adsk.core.Application.get().log(args.firingEvent.name) # マウスカーソルの下の要素の選択 sel: adsk.core.Selection = args.selection sktPnt: adsk.fusion.SketchPoint = sel.entity # ジオメトリの取得 pnt: adsk.core.Point3D = sktPnt.worldGeometry # 条件で選択を判断する if pnt.y < 0: # Y座標がマイナスの場合、選択を許さない args.isSelectable = False
"args.selection" は、SelectionCommandInput.selectionと同じ型のオブジェクトに
なります。
Fusion 360 Help
カーソル下の実際の要素は args.selection.entity となる為、注意が必要です。
"条件で選択を判断する" で座標値を判断していますが、args.isSelectable
プロパティで選択可能か?不可能か?を設定する事が出来ます。
これがフィルターとして機能します。
args.isSelectableはデフォルトがTrue(選択可能)として受け取っているので、
選択可の場合は態々Trueにする必要も無いでしょう。
又、SelectionCommandInputのaddSelectionFilterでフィルターを設定している場合、
preSelectイベントは発生しません。
(今回の場合であれば、ボディの面の上にマウスカーソルを持って行っても
イベントは発生しません。)
より細かなフィルターを体感する
では、XY平面上にスケッチを作成し、原点を中心に幾つかの点を作成して
実行してみて下さい。
赤がX軸、緑がY軸です。
先程の条件からして、青の領域部分は選択可能で、赤の領域部分は選択不可能に
なっている事が確認出来ると思います。
今回の条件が非現実的なのですが、より細かなフィルターは事前選択で
作り出すことが可能と言う事になります。
但し、1個選択した時点でプレビューは表示されていますし、OKボタンが押せる
状態です。OKボタンを制御する必要がありますね。
OKボタンを制御する
OKボタンを有効にする為の条件は
"3点以上の点が選択され、1個以上はX座標がプラスの必要がある。"
としました。要は
青領域の中の3個以上でかつ、紫領域から1個以上選択されている状態で
OKボタンを押せるようにします。無理やりな条件ですね。
OKボタンの制御は、validateInputsイベントで行います。
Fusion 360 Help
逆の表現をすれば、validateInputsイベントでしか行えません。
ドキュメント通りにイベントハンドラを登録します。
・・・ # preSelectイベントハンドラー登録 onPreSelect = MyPreSelectHandler() cmd.preSelect.add(onPreSelect) _handlers.append(onPreSelect) # validateInputsイベントハンドラー登録 onValidateInputs = MyValidateInputsHandler() cmd.validateInputs.add(onValidateInputs) _handlers.append(onValidateInputs) ・・・
続いてハンドラーです。
class MyValidateInputsHandler(adsk.core.ValidateInputsEventHandler): def __init__(self): super().__init__() def notify(self, args: adsk.core.ValidateInputsEventArgs): adsk.core.Application.get().log(args.firingEvent.name) # ダイアログ上の全てのcommand inputsを取得 inputs: adsk.core.CommandInputs = args.inputs # IDでSelectionCommandInputを取得 selIpt: adsk.core.SelectionCommandInput = inputs.itemById('selIpt') # 選択数が3個未満の場合はOKボタンを抑制する。 selCount: int = selIpt.selectionCount if selCount < 3: args.areInputsValid = False return # 選択された全てのスケッチ点取得 sktPnts = [selIpt.selection(idx).entity for idx in range(selCount)] # X座標がプラスの物のみの点を取得 p: adsk.fusion.SketchPoint xPulsPnts = [p for p in sktPnts if not p.worldGeometry.x < 0] # X座標プラスの点が1個も無い場合はOKボタンを抑制する。 if len(xPulsPnts) < 1: args.areInputsValid = False
adsk.core.ValidateInputsEventArgs.areInputsValidがOKボタンの有効/無効を
管理しているプロパティです。
Fusion 360 Help
再度記載しますが、このプロパティでしか制御出来ません。
必要条件を満たしているかどうかをチェックし、満たしていない場合はFalseに
します。デフォルト値はTrueの為、条件を満たしている場合は特に何かをする必要は
無いでしょう。
これで実際にスクリプトを実行して頂くと、最初に提案した非現実的な条件を満たす
までは、プレビューもされずOKボタンを押せない状態になりました。
まとめ
最終的な全てのコードはこの様になりました。
# Author- # Description- # Fusion360API Python script import traceback import adsk.fusion import adsk.core _app: adsk.core.Application = None _ui: adsk.core.UserInterface = None _handlers = [] def run(context): try: global _app, _ui _app = adsk.core.Application.get() _ui = _app.userInterface cmdDef: adsk.core.CommandDefinition = _ui.commandDefinitions.itemById( 'test_cmd_id' ) if not cmdDef: cmdDef = _ui.commandDefinitions.addButtonDefinition( 'test_cmd_id', 'ダイアログです', 'ツールチップです' ) 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())) class MyCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): def __init__(self): super().__init__() def notify(self, args: adsk.core.CommandCreatedEventArgs): adsk.core.Application.get().log(args.firingEvent.name) try: global _handlers cmd: adsk.core.Command = adsk.core.Command.cast(args.command) # event onDestroy = MyCommandDestroyHandler() cmd.destroy.add(onDestroy) _handlers.append(onDestroy) onExecutePreview = MyExecutePreviewHandler() cmd.executePreview.add(onExecutePreview) _handlers.append(onExecutePreview) # preSelectイベントハンドラー登録 onPreSelect = MyPreSelectHandler() cmd.preSelect.add(onPreSelect) _handlers.append(onPreSelect) # validateInputsイベントハンドラー登録 onValidateInputs = MyValidateInputsHandler() cmd.validateInputs.add(onValidateInputs) _handlers.append(onValidateInputs) # inputs inputs: adsk.core.CommandInputs = cmd.commandInputs selIpt: adsk.core.SelectionCommandInput = inputs.addSelectionInput( 'selIpt', '点を選択', 'スケッチの点を選択して下さい' ) selIpt.addSelectionFilter('SketchPoints') except: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) class MyValidateInputsHandler(adsk.core.ValidateInputsEventHandler): def __init__(self): super().__init__() def notify(self, args: adsk.core.ValidateInputsEventArgs): adsk.core.Application.get().log(args.firingEvent.name) # ダイアログ上の全てのcommand inputsを取得 inputs: adsk.core.CommandInputs = args.inputs # IDでSelectionCommandInputを取得 selIpt: adsk.core.SelectionCommandInput = inputs.itemById('selIpt') # 選択数が3個未満の場合はOKボタンを抑制する。 selCount: int = selIpt.selectionCount if selCount < 3: args.areInputsValid = False return # 選択された全てのスケッチ点取得 sktPnts = [selIpt.selection(idx).entity for idx in range(selCount)] # X座標がプラスの物のみの点を取得 p: adsk.fusion.SketchPoint xPulsPnts = [p for p in sktPnts if not p.worldGeometry.x < 0] # X座標プラスの点が1個も無い場合はOKボタンを抑制する。 if len(xPulsPnts) < 1: args.areInputsValid = False # 事前選択です! class MyPreSelectHandler(adsk.core.SelectionEventHandler): def __init__(self): super().__init__() def notify(self, args: adsk.core.SelectionEventArgs): adsk.core.Application.get().log(args.firingEvent.name) # マウスカーソルの下の要素の選択 sel: adsk.core.Selection = args.selection sktPnt: adsk.fusion.SketchPoint = sel.entity # ジオメトリの取得 pnt: adsk.core.Point3D = sktPnt.worldGeometry # 条件で選択を判断する if pnt.y < 0: # Y座標がマイナスの場合、選択を許さない args.isSelectable = False class MyExecutePreviewHandler(adsk.core.CommandEventHandler): def __init__(self): super().__init__() def notify(self, args: adsk.core.CommandEventArgs): adsk.core.Application.get().log(args.firingEvent.name) selIpt: adsk.core.SelectionCommandInput = args.command.commandInputs.itemById( 'selIpt') pnts = [selIpt.selection(idx).entity.worldGeometry for idx in range( selIpt.selectionCount)] createSphereBodies(pnts) args.isValidResult = True def createSphereBodies(pnts: list): radius = 1 tmpMgr: adsk.fusion.TemporaryBRepManager = adsk.fusion.TemporaryBRepManager.get() sphereBodies: list = [] pnt: adsk.core.Point3D for pnt in pnts: sphere: adsk.fusion.BRepBody = tmpMgr.createSphere(pnt, radius) sphereBodies.append(sphere) app: adsk.core.Application = adsk.core.Application.get() des: adsk.fusion.Design = app.activeProduct root: adsk.fusion.Component = des.rootComponent isParametric: bool = True if des.designType == adsk.fusion.DesignTypes.DirectDesignType: isParametric = False occ: adsk.fusion.Occurrence = root.occurrences.addNewComponent( adsk.core.Matrix3D.create() ) comp: adsk.fusion.Component = occ.component baseFeat: adsk.fusion.BaseFeature = None if isParametric: baseFeat = comp.features.baseFeatures.add() bodies: adsk.fusion.BRepBodies = comp.bRepBodies if isParametric: baseFeat.startEdit() for body in sphereBodies: bodies.add(body, baseFeat) baseFeat.finishEdit() else: for body in sphereBodies: bodies.add(body) class MyCommandDestroyHandler(adsk.core.CommandEventHandler): def __init__(self): super().__init__() def notify(self, args: adsk.core.CommandEventArgs): adsk.core.Application.get().log(args.firingEvent.name) adsk.terminate()
OKボタンを制御する為には "ValidateInputs" イベントを使用する事は、
知られているのですが、より細かな選択フィルターを作る為には
"preSelect" 又は "preSelectMouseMove" を使用する方法はあまり
知られていないような印象が有ります。
次回も引き続きSelectionCommandInputを扱います。実はバグでは無いかと
思う部分が有る為、それを避ける為の方法をご紹介します。