C#ATIA

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

Fusion 360 Addin Helper

こちらのVSCodeの拡張ですが、Fusion360のアドイン開発用の
物っぽいのですが
GitHub - HiceS/fusion-360-helper: Fusion 360 Visual Studio Code Extension to create Addins and link intellisense automatically
えーと、何だろう???
今の所、不自由は感じてないからスルーかな・・・。

この方、Autodeskの人なのかな?
他にも凄そうなものを公開してくれてますね。
理解出来ていませんが。

フィレットの元のエッジを探せ!!2

こちらの続きです。
フィレットの元のエッジを探せ!!1 - C#ATIA

前回の物を修正しました。

# Fusion360API Python script

import traceback
import adsk.fusion
import adsk.core


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

        msg: str = 'Select'
        selFilter: str = 'Features'
        sel: adsk.core.Selection = selectEnt(msg, selFilter)
        if not sel:
            return

        useFeats = getReferenced_Fillet(sel.entity)

        [app.log(f.name) for  f in useFeats]


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


def selectEnt(
        msg: str,
        filterStr: str) -> adsk.core.Selection:

    try:
        app: adsk.core.Application = adsk.core.Application.get()
        ui: adsk.core.UserInterface = app.userInterface
        sel = ui.selectEntity(msg, filterStr)
        return sel
    except:
        return None


def getReferenced_Fillet(self) -> list:
        app: adsk.core.Application = adsk.core.Application.get()
        des: adsk.fusion.Design = app.activeProduct

        def getEdgeTokenSet(edgeSets: adsk.fusion.FilletEdgeSets) -> set:
            tokens = set()
            # 
            try:
                for edgeset in edgeSets:
                    [tokens.add(e.entityToken) for e in edgeset.edges]
            except:
                pass

            return tokens

        # *****
        timeline: adsk.fusion.Timeline = des.timeline
        backupMarker = timeline.markerPosition

        timeObj: adsk.fusion.TimelineObject = self.timelineObject
        timeObj.rollTo(True)

        edgeTokenSet = getEdgeTokenSet(self.edgeSets)

        timeObjs = [timeline.item(idx) for idx in range(timeline.markerPosition)]

        useFeats = []
        for timeObj in timeObjs:
            timeEnt: adsk.fusion.Feature = timeObj.entity
            if not hasattr(timeEnt, 'bodies'):
                continue

            timeObj.rollTo(False)
            tmpTokenSet = set()
            for body in timeEnt.bodies:
                [tmpTokenSet.add(e.entityToken) for e in body.edges]

            intersection = edgeTokenSet.intersection(tmpTokenSet)
            if len(intersection) > 0:
                useFeats.append(timeEnt)
                [edgeTokenSet.discard(token) for token in intersection]

            if len(edgeTokenSet) < 1:
                break

        timeline.markerPosition = backupMarker

        return useFeats

フィレットを付けている元のエッジのentityTokenを調べ、
タイムラインの先頭から、該当するentityTokenを持っている
最初のフィーチャが、エッジを作ったフィーチャだと
判断しており、恐らく正しいと思います。

但し、

フィレットのタイプが "フィレット"で、エッジを指定している時のみ
だけです。
タイプ ”フィレット” で面を指定しているものは情報がとれません。
タイプ ”ルールドフィレット” "フルラウンドフィレット" も情報がとれません。
(上記のスクリプトではエラーになります)
役立たず とまでは言いませんが、先が思いやられる・・・。

フィレットにとって "子" 側の要素って何だろう?
もう、定義からして迷う・・・。

フィレットの元のエッジを探せ!!1

タイトルが若干過剰です。

フィレットを付けた際の元のエッジがどのフィーチャで作成されたか?
を探し出します。

取りあえず作りましたが、これは正しくない事が分かりました。
が、無くしてしまいそうなので、とりあえず書き残しておきます。

# Fusion360API Python script

import traceback
import adsk.fusion
import adsk.core


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

        msg: str = 'Select'
        selFilter: str = 'Features'
        sel: adsk.core.Selection = selectEnt(msg, selFilter)
        if not sel:
            return

        timeline: adsk.fusion.Timeline = des.timeline
        backupMarker = timeline.markerPosition

        feat: adsk.fusion.Feature = sel.entity
        timeObj: adsk.fusion.TimelineObject = feat.timelineObject
        timeObj.rollTo(True)
        edgeSets = [es for es in sel.entity.edgeSets]

        edgeTokens =[]
        for edgeset in edgeSets:
            edgeTokens.extend([e.entityToken for e in edgeset.edges])

        timeObjs = [timeline.item(idx) for idx in range(timeline.markerPosition)]

        useFeats = []
        for timeObj in timeObjs[::-1]:
            timeEnt: adsk.fusion.Feature = timeObj.entity
            if not hasattr(timeEnt, 'bodies'):
                continue

            timeObj.rollTo(False)
            tmpTokens = []
            for body in timeEnt.bodies:
                tmpTokens.extend([e.entityToken for e in body.edges])
            if any([token in edgeTokens for token in tmpTokens]):
                useFeats.append(timeEnt)

        [app.log(f.name) for  f in useFeats[::-1]]

        timeline.markerPosition = backupMarker

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


def selectEnt(
        msg: str,
        filterStr: str) -> adsk.core.Selection:

    try:
        app: adsk.core.Application = adsk.core.Application.get()
        ui: adsk.core.UserInterface = app.userInterface
        sel = ui.selectEntity(msg, filterStr)
        return sel
    except:
        return None

フィレットフィーチャ以外も選択出来ますが、無意味です。
又、修正する為のメモ書きですが、
・タイムラインの後ろからの検索はNG。先頭からにすべし。
・エッジを見つけたら、edgeTokensリストから除去しないと
 重複して見つけ出してしまう。あくまで最初のフィーチャを
 見つけ出す。

タイムラインの長さにもよりますが、まぁワンテンポ待ちぐらい
なので耐えられない事は無いぐらいの時間でした。
”作成” 側のコマンドは親子を見る必要が有るけど、"修正" 側の
コマンドは親だけ見つければ良いのかな?

要素の依存関係6

こちらの続きです。
要素の依存関係5 - C#ATIA

前回の方向性の悩みを投げてみました。
Re: List of Dependent Features - Page 2 - Autodesk Community
温かいですね。色々とご意見頂けました。
そうか、全体じゃなくて親子だけでも良いのか。

親子だけにしろ全体にしろ、結局はタイムラインマーカーを
移動させない事には、正確な情報を得ることが出来ない事には
変わりありません。

チラッとリプライに書きましたが、マーカーの移動は
必ず再計算されるわけでは無く、再計算が必要となったものが
計算される仕組みだったはずです。
カスタムフィーチャに取り組んだ際に得た知識です。

再計算が必要無ければ、それ程時間がかからないのでは?
と思い、テストしました。

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


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

        t = time.time()
        for tl in des.timeline:
            tl.rollTo(True)
        des.timeline.moveToEnd()

        app.log(f'{time.time() - t}')

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

本当に移動させただけです。

ビビる程でも無かったです。

実際は何らかの情報を取得する必要が有るので、もうちょっと
時間がかかるはずですが、想像していたより待たされない気はしてます。

2021年CADのシェア

恐らく独自調査ではないかな?と思われますが、
2021年CADのシェアだそうです。
CNCCookbook 2021 CAD Survey [ Market Share, Customer Satisfaction ] - CNCCookbook: Be A Better CNC'er

CATIA低いな・・・ハイエンドだとCreoが一歩リードしている
様な感じでしょうか?Creoは一度だけ客先で見たことは
あります。NXは見た事すらないですね。

マウスカーソル位置を表現したい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

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

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

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

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