C#ATIA

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

サーフェスの色をボディに反映する Fusion360

こちらのCATIAマクロをFusion360でも利用したい気持ちが山々です。
サーフェスの色をボディに反映する2 - C#ATIA

Fusion360もCATIA同様に、サーフェスをソリッド化した際に面の色は
引き継いでもらえません。(恐らく一番最初の面の色に統一)
CADにとって面の色は重要な要素だと思い込んでいます。

この処理を行う際、同一面を検索する必要があるのですが、既に
こちらでその処理は行った事があります。
3D間違い探しを解く2 - C#ATIA

ハードル的には高くは無さそうなので作ってみました。が、

# FusionAPI_python 
# Author-kantoku

import adsk.core, adsk.fusion, traceback
import bisect as bs
import time

from .Fusion360Utilities.Fusion360Utilities import AppObjects
from .Fusion360Utilities.Fusion360CommandBase import Fusion360CommandBase

# CommandInputs
_selTgtInfo = ['dlgSelTgt','対象ボディ','色を変更するボディを選択']
_selRefInfo = ['dlgSelRef','参照コンポーネント','参照元となるコンポーネントを選択']

class ColorRestorationCore(Fusion360CommandBase):
    _handlers = []

    def __init__(self, cmd_def, debug):
        super().__init__(cmd_def, debug)
        pass

    def on_preview(self, command: adsk.core.Command, inputs: adsk.core.CommandInputs, args, input_values):
        pass

    def on_destroy(self, command: adsk.core.Command, inputs: adsk.core.CommandInputs, reason, input_values):
        pass

    def on_input_changed(self, command: adsk.core.Command, inputs: adsk.core.CommandInputs, changed_input, input_values):
        pass

    def on_execute(self, command: adsk.core.Command, inputs: adsk.core.CommandInputs, args, input_values):
        ao = AppObjects()
        try:
            global _selTgtInfo, _selRefInfo
            # target
            selTgt :adsk.core.SelectionCommandInput = inputs.itemById(_selTgtInfo[0])

            # reference
            selRef :adsk.core.SelectionCommandInput = inputs.itemById(_selRefInfo[0])

            fact = ColorRestorationFactry(
                selTgt.selection(0).entity,
                selRef.selection(0).entity)

            fact.exec()

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

    def on_create(self, command: adsk.core.Command, inputs: adsk.core.CommandInputs):
        ao = AppObjects()

        # comp Position check
        command.isPositionDependent = True

        # -- event --
        onValid = self.ValidateInputHandler()
        command.validateInputs.add(onValid)
        self._handlers.append(onValid)

        onPreSelect = self.PreSelectHandler()
        command.preSelect.add(onPreSelect)
        self._handlers.append(onPreSelect)

        # -- Dialog --
        global _selTgtInfo, _selRefInfo
        # target
        selTgt :adsk.core.SelectionCommandInput = inputs.addSelectionInput(
            _selTgtInfo[0], _selTgtInfo[1], _selTgtInfo[2])
        selTgt.setSelectionLimits(0)
        selTgt.addSelectionFilter('Bodies')

        # reference
        selRef :adsk.core.SelectionCommandInput = inputs.addSelectionInput(
            _selRefInfo[0], _selRefInfo[1], _selRefInfo[2])
        selRef.setSelectionLimits(0)
        selRef.addSelectionFilter('Occurrences')

    # -- Support class --
    class PreSelectHandler(adsk.core.SelectionEventHandler):
        def __init__(self):
            super().__init__()

        def notify(self, args):
            try:
                args = adsk.core.SelectionEventArgs.cast(args)
                inputs = args.activeInput.commandInputs

                actIpt = adsk.core.SelectionCommandInput.cast(
                    args.firingEvent.activeInput)
                if not actIpt: return

                global _selTgtInfo, _selRefInfo
                # target
                selTgt :adsk.core.SelectionCommandInput = inputs.itemById(_selTgtInfo[0])

                # reference
                selRef :adsk.core.SelectionCommandInput = inputs.itemById(_selRefInfo[0])

                # -- target --
                if actIpt.id == _selTgtInfo[0]:
                    if selTgt.selectionCount > 0:
                        args.isSelectable = False
                        return

                    # comp
                    ent = args.selection.entity
                    comp = ent.parentComponent

                    # Unmatched comp
                    if selRef.selectionCount < 1:
                        return

                    tgt = selRef.selection(0).entity
                    if comp == tgt.parentComponent:
                        args.isSelectable = False
                        return

                # -- reference --
                if actIpt.id == _selRefInfo[0]:
                    if selRef.selectionCount > 0:
                        args.isSelectable = False
                        return

                    # comp
                    ent = args.selection.entity
                    comp = adsk.fusion.Component.cast(ent)
                    if comp is None:
                        occ = adsk.fusion.Occurrence.cast(ent)
                        comp = occ.component

                    # bRepBodies.count
                    if comp.bRepBodies.count < 1:
                        args.isSelectable = False
                        return

                    # Unmatched comp
                    if selTgt.selectionCount < 1:
                        return

                    tgt = selTgt.selection(0).entity
                    if comp == tgt.parentComponent:
                        args.isSelectable = False
                        return

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

    class ValidateInputHandler(adsk.core.ValidateInputsEventHandler):
        def __init__(self):
            super().__init__()
        def notify(self, args):
            inputs = args.inputs

            global _selTgtInfo, _selRefInfo
            # target
            selTgt :adsk.core.SelectionCommandInput = inputs.itemById(_selTgtInfo[0])

            # reference
            selRef :adsk.core.SelectionCommandInput = inputs.itemById(_selRefInfo[0])

            if selTgt.selectionCount > 0 and selRef.selectionCount > 0:
                args.areInputsValid = True
            else:
                args.areInputsValid = False

class ColorRestorationFactry():
    _tgtFaces :list = []
    _refFaces :list = []
    _mat :adsk.core.Matrix3D = None

    def __init__(
        self,
        tgtBody :adsk.fusion.BRepBody,
        refOcc :adsk.fusion.Occurrence):

        occ = tgtBody.assemblyContext
        mat = adsk.core.Matrix3D.create()

        if occ:
            mat = occ.transform

        self._mat = self.getMatrix(
            mat,
            refOcc.transform)

        self._tgtFaces = [f for f in tgtBody.faces]

        lst = []
        for bdy in refOcc.bRepBodies:
            for f in bdy.faces:
                lst.append(f)
        self._refFaces = lst

    def exec(
        self,
        cogTolerance = 0.001,
        areaTolerance = 0.001):

        ao = AppObjects()
        try:
            adsk.fusion.BRepFace.isOverRap = False
            adsk.fusion.BRepFace.transform_centroid = None

            face = adsk.fusion.BRepFace.cast(None)
            tgtFaces = sorted(self._tgtFaces, key=lambda v: v.area)
            refFaces = sorted(self._refFaces, key=lambda v: v.area)
            refAreas = [face.area for face in refFaces]

            for face in refFaces:
                face.transform_centroid = self.transformByClone(
                    face.centroid, self._mat)

            ref = adsk.fusion.BRepFace.cast(None)
            tgtCog = adsk.core.Point3D.cast(None)
            for face in tgtFaces:
                if face.isOverRap:
                    continue

                lwr = bs.bisect_left(refAreas, face.area - areaTolerance)
                upr = bs.bisect_right(refAreas, face.area + areaTolerance)

                tgtCog = face.centroid
                rangeFaces = [ref for ref in refFaces[lwr : upr] if not ref.isOverRap]
                for ref in rangeFaces:
                    if tgtCog.isEqualToByTolerance(
                        ref.transform_centroid,
                        cogTolerance):
                        
                        face.appearance = ref.appearance
                        ref.isOverRap = True

            vp :adsk.core.Viewport = AppObjects().app.activeViewport
            vp.refresh()
        except:
            ao.ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

    # 補正後の重心
    def transformByClone(
        self,
        pnt :adsk.core.Point3D,
        mat :adsk.core.Matrix3D) -> adsk.core.Point3D:

        p = pnt.copy()
        p.transformBy(mat)

        return p

    # 位置ずれ
    def getMatrix(
        self,
        fromMat :adsk.core.Matrix3D,
        toMat :adsk.core.Matrix3D) -> adsk.core.Matrix3D:

        (f_ori, f_x, f_y, f_z) = fromMat.getAsCoordinateSystem()
        (t_ori, t_x, t_y, t_z) = toMat.getAsCoordinateSystem()
        mat = adsk.core.Matrix3D.create()
        mat.setToAlignCoordinateSystems(
            t_ori, t_x, t_y, t_z,
            f_ori, f_x, f_y, f_z)
        return mat

劇的に遅いです・・・。原因が分からないのですが、使い物にならないほど
遅いんです。

同一面を見つけながら、見付けた時点で色を反映しているのですが、
まとめて色を変える方法は恐らく無いと思うのですが。
1枚色を反映する毎にグラフィックの更新がかかっている感じがするので、
それを食い止めたいのですが、方法がみつからないんです。