C#ATIA

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

MeshBodyをBRepBodyに変換1

先日のドロップダウンリストが出来るようになったので、
今回は副産物を作る事にしました。
ドロップダウンリストを変更する - C#ATIA

個人的には、STL(だけではなくOBJ等も)データをソリッド/サーフェス
に変換しても、重たいだけで役には立たないと思っているのですが、
世の中には需要があるのも確かです。こちらもそうですね。
Convert Mesh to BRep in API - Autodesk Community

Fusion360にはその為のコマンドが用意されているものの、50000枚以下
と制限が有ります。・・・その処理を必要とする場合は恐らく50000枚は
軽く超えているハズです。

その為、変換可能なサイズまでぶった切り、複数個にして変換する為の
スクリプトを作成しました。

# Fusion360API Python script
# Author-kantoku
# Description-Meshをぶった切りつつBRep化

import adsk.core, adsk.fusion, traceback

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)

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

        if des.designType == adsk.fusion.DesignTypes.ParametricDesignType:
            query = _ui.messageBox(
                'ダイレクトモードに切り替えます。\nよろしいですか?',
                'ダイレクトモードに切り替える必要があります',
                adsk.core.MessageBoxButtonTypes.OKCancelButtonType,
                adsk.core.MessageBoxIconTypes.QuestionIconType)

            if query == adsk.core.DialogResults.DialogCancel:
                return
            else:
                des.designType = adsk.fusion.DesignTypes.DirectDesignType

        msg :str = 'メッシュボディを選択して下さい!'
        selFiltter :str = 'MeshBodies'
        sel :adsk.core.Selection = selectEnt(msg ,selFiltter)
        if not sel: return
        mesh = sel.entity

        fact = Mesh2BRepFactry(mesh)
        bodies = fact.toBrep()

        _ui.messageBox(f'{len(bodies)}個のBRepボディを作成しました')

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

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

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

def dumpMsg(msg :str):
    adsk.core.Application.get().userInterface.palettes.itemById('TextCommands').writeText(str(msg))



class Mesh2BRepFactry:
    def __init__(self, mesh :adsk.fusion.MeshBody):
        self.mesh = mesh

    def toBrep(self, maxCount = 10000) -> list:
        comp :adsk.fusion.Component = self.mesh.parentComponent

        if maxCount > 10000:
            maxCount = 10000

        # split
        meshLst = self._MeshsSplit(self.mesh, maxCount)

        # toBrep
        return self._Mesh2BRepCommand(meshLst)


    def _Mesh2BRepCommand(
        self,
        meshLst :list) -> list:

        app :adsk.core.Application = adsk.core.Application.get()
        ui :adsk.core.UserInterface = app.userInterface
        sels :adsk.core.Selections = ui.activeSelections
        comp :adsk.fusion.Component = self.mesh.parentComponent
        exceptionLst = [b for b in comp.bRepBodies]

        for mesh in meshLst:
            sels.clear()
            sels.add(mesh)
            app.executeTextCommand(u'Commands.Start Mesh2BRepCommand')
            app.executeTextCommand(u'NuCommands.CommitCmd')

            adsk.doEvents()
        
        return self._list_difference([b for b in comp.bRepBodies], exceptionLst)


    def _MeshsSplit(
        self,
        mesh :adsk.fusion.MeshBody,
        maxCount) -> list:

        comp :adsk.fusion.Component = self.mesh.parentComponent

        meshLst = [self.mesh]

        exceptionLst = [m for m in comp.meshBodies]
        exceptionLst.remove(self.mesh)

        reOpeFG = False
        while True:
            for mesh in meshLst:
                dumpMsg(f'{mesh.name}:{mesh.displayMesh.triangleCount}')
                if maxCount < mesh.displayMesh.triangleCount:
                    tool :adsk.fusion.ConstructionPlane = self._getSplitPlane(mesh)
                    self._MeshPlaneCutCommand(mesh, tool)
                    tool.deleteMe()
                    reOpeFG = True

                adsk.doEvents()
            
            if reOpeFG:
                meshLst = self._list_difference([m for m in comp.meshBodies], exceptionLst)
                reOpeFG = False
            else:
                break
        
        return meshLst


    def _MeshPlaneCutCommand(
        self,
        mesh :adsk.fusion.MeshBody,
        plane :adsk.fusion.ConstructionPlane):

        app :adsk.core.Application = adsk.core.Application.get()
        ui :adsk.core.UserInterface = app.userInterface
        sels :adsk.core.Selections = ui.activeSelections

        sels.clear()
        app.executeTextCommand(u'Commands.Start MeshPlaneCutCommand')

        app.executeTextCommand(u'UI.EnableCommandInput MeshPlaneCutBodySel')
        sels.add(mesh)

        app.executeTextCommand(u'UI.EnableCommandInput MeshPlaneCutPlaneSel')
        sels.add(plane)

        app.executeTextCommand(u'Commands.SetString infoCutType MeshPlaneCutSplitBody')
        app.executeTextCommand(u'Commands.SetString infoFillType MeshPlaneCutFillNone')

        app.executeTextCommand(u'NuCommands.CommitCmd')


    def _list_difference(self, list1, list2):
        result = list1.copy()
        for value in list2:
            if value in result:
                result.remove(value)

        return result


    def _getSplitPlane(
        self,
        mesh :adsk.fusion.MeshBody) -> adsk.fusion.ConstructionPlane:

        comp :adsk.fusion.Component = mesh.parentComponent

        pnts = [p for p in mesh.displayMesh.nodeCoordinates]
        bound = adsk.core.BoundingBox3D.create(pnts[0], pnts[1])
        for p in pnts[2:]:
            bound.expand(p)

        pntMin :adsk.core.Point3D = bound.minPoint
        pntMax :adsk.core.Point3D = bound.maxPoint

        pntMid = adsk.core.Point3D.create(
            (pntMin.x + pntMax.x) * 0.5,
            (pntMin.y + pntMax.y) * 0.5,
            (pntMin.z + pntMax.z) * 0.5)

        width = [abs(pntMax.x) + abs(pntMin.x), comp.xConstructionAxis]
        length = [abs(pntMax.y) + abs(pntMin.y), comp.yConstructionAxis]
        height = [abs(pntMax.z) + abs(pntMin.z), comp.zConstructionAxis]

        direction = max([width, length, height], key=(lambda x: x[0]))

        plane = adsk.core.Plane.create(pntMid, direction[1].geometry.direction)
        conPlanes :adsk.fusion.ConstructionPlanes = comp.constructionPlanes
        planeIpt :adsk.fusion.ConstructionPlaneInput = conPlanes.createInput()
        planeIpt.setByPlane(plane)

        return conPlanes.add(planeIpt)

上記で50000枚以下と記載しましたが、10000枚を超えるとこの様な
警告のダイアログが出現します。
f:id:kandennti:20210427171109p:plain
どうせなら放置したい方なのでダイアログを閉じたいのですが、
方法が見つかりませんでした。
その為、10000枚以下になるまでぶった切りまくります。

後はIgesなりStepなりでエクスポートして下さいませ。