C#ATIA

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

マウスカーソル位置を表現したい2

少し前なのですが、こちらの続きです。
マウスカーソル位置を表現したい1 - C#ATIA
どうしても気になっていたので。


最近読んだ本で、JavascriptのsetTimeoutの意味合いを知りました。
ひょっとしたら知らない間に使っているかも・・・。

前回のクラッシュの原因は、イベントが短時間に発生し過ぎる事と
感じていたので、正にsetTimeoutが合致すると。

pythonではどうするの? と思い探した所、こちらを見つけました。
pythonでJSのsetTimeoutみたいなこと - Qiita
・・・asyncio理解出来ませんでした。

Fusion360のイベントの発生自体を制御する事は難しいですが、
イベントハンドラー内の処理を何らかの方法で制御すれば良い
はず。
極論、非同期処理でフラッグを切り替えちゃえば良いのでは?
と思い、threadingを利用する事にしました。

この様な感じです。

# Fusion360API Python script

import traceback
import adsk.fusion
import adsk.core
import threading

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

_selIpt: adsk.core.SelectionCommandInput = None
_cgFact: 'CustomGraphicsFactry' = None

# スレッド停止用
_stopFlag = None

# イベントを有効にさせる間隔(単位 秒)
EVENT_TIMER = 0.5

# イベントを有効にするフラッグ
_eventCoordination = False

class MyThread(threading.Thread):
    def __init__(self, event, timer):
        threading.Thread.__init__(self)
        self.stopped = event
        self.timer = timer
    def run(self):
        while not self.stopped.wait(self.timer):
            global _eventCoordination
            _eventCoordination = False
            adsk.core.Application.get().log('-----Thread-----')


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)
            inputs: adsk.core.CommandInputs = cmd.commandInputs

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

            onExecute = MyExecuteHandler()
            cmd.execute.add(onExecute)
            _handlers.append(onExecute)

            onPreSelect = MyPreSelectHandler()
            # cmd.preSelect.add(onPreSelect)
            cmd.preSelectMouseMove.add(onPreSelect)
            _handlers.append(onPreSelect)


            global _selIpt
            _selIpt = inputs.addSelectionInput(
                'selIptId',
                'select',
                'select'
            )

            global _cgFact
            _cgFact = CustomGraphicsFactry('test')

            # イベント抑制用のスレッド
            global _stopFlag        
            _stopFlag = threading.Event()
            myThread = MyThread(_stopFlag, EVENT_TIMER)
            myThread.start()

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


class MyExecuteHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()

    def notify(self, args: adsk.core.CommandEventArgs):
        adsk.core.Application.get().log(args.firingEvent.name)


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)

        try:
            global _stopFlag
            _stopFlag.set()
        except:
            pass

        adsk.terminate()


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

        cmdDef: adsk.core.CommandDefinition = _ui.commandDefinitions.itemById(
            'test_cmd'
        )

        if not cmdDef:
            cmdDef = _ui.commandDefinitions.addButtonDefinition(
                'test_cmd',
                'Test',
                'Test'
            )

        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 MyPreSelectHandler(adsk.core.SelectionEventHandler):
    def __init__(self):
        super().__init__()
        self.count = 1
    def notify(self, args: adsk.core.SelectionEventArgs):
        adsk.core.Application.get().log(f'{args.firingEvent.name}:{self.count}')
        self.count += 1

        global _eventCoordination
        if _eventCoordination:
            return

        adsk.core.Application.get().log('*** event on')

        tmpMgr: adsk.fusion.TemporaryBRepManager = adsk.fusion.TemporaryBRepManager.get()

        entity = args.selection.entity
        if not entity.objectType == adsk.fusion.BRepBody.objectType:
            if hasattr(entity, 'body'):
                entity = entity.body
            else:
                return

        clone = tmpMgr.copy(entity)

        tmpMgr.booleanOperation(
            clone,
            tmpMgr.createSphere(
                args.selection.point,
                0.5
            ),
            adsk.fusion.BooleanTypes.IntersectionBooleanType
        )

        global _cgFact
        _cgFact.updateBody(clone)

        _eventCoordination = True


class CustomGraphicsFactry():

    def __init__(
        self,
        id :str,):

        self.app: adsk.core.Application = adsk.core.Application.get()
        self.cgGroup: adsk.fusion.CustomGraphicsGroup = None
        self.color = adsk.fusion.CustomGraphicsSolidColorEffect.create(
            adsk.core.Color.create(255,0,0,255)
        )
        self.id = id
        self.refreshCG()

    def __del__(
        self):

        self.removeCG()

    def removeCG(
        self):

        des :adsk.fusion.Design = self.app.activeProduct
        cgs = [cmp.customGraphicsGroups for cmp in des.allComponents]
        cgs = [cg for cg in cgs if cg.count > 0]
        
        if len(cgs) < 1: return

        for cg in cgs:
            gps = [c for c in cg]
            gps.reverse()
            for gp in gps:
                if not hasattr(gp, 'id'):
                    continue

                if not gp.id == self.id:
                    continue

                gp.deleteMe()

    def refreshCG(
        self
        ):

        self.removeCG()
        des :adsk.fusion.Design = self.app.activeProduct
        comp :adsk.fusion.Component = des.activeComponent
        self._cgGroup: adsk.fusion.CustomGraphicsGroup = comp.customGraphicsGroups.add()
        self._cgGroup.id = self.id

    def updateBody(
        self,
        body: adsk.fusion.BRepBody,
        isRefresh :bool = True
        ):

        if isRefresh:
            self.refreshCG()

        cgBody: adsk.fusion.CustomGraphicsBRepBody = self._cgGroup.addBRepBody(body)
        cgBody.color = self.color

こちらは前回の状態です。あっさりクラッシュします。

こちらはスレッドで抑え込んだ方です。

赤い点がマウスの動きに対して少し遅れ気味で、
処理を間引いている事を感じます。
結構粘りましたが、結局はクラッシュしました。

イケルと思ってたんですけどね。ハンドラー内の処理が重すぎかな?