久々にFusion360です。
こちらに挑戦してみました。
Solved: Use Selection Input to select a single triangle in a mesh? - Autodesk Community
MeshBodyの小さな一面をAPIで選択出来ないか? と言った内容です。
実際に面を選択させるのではなく、選択したように見えれば良いようです。
最初は全ての頂点の中から、クリックした位置に近い3点で面を作れば
それっぽく出来るだろう と思っていたのですが、精度が非常に悪い
又は、一つの面とならない3点が選ばれたりしたため、もっとまじめに
考えました。
Fusion360API的にはMeshBody(インポートされたSTL等)は
頂点と頂点の組み合わせの情報しか提供していません。
つまり面の情報そのものは提供しない為、作り出す必要があります。
Fusion 360 Help
幸いこちらで、それらの処理を行った事が有りました。
Re: Silhouette of a mesh body - Autodesk Community
全ての三角面を作り、クリックした点に一番近いものを選び出そうとも
思ったのですが、処理時間が長く断念。
時間のかかる一番の原因は全ての三角面を作っている為です。
そこで思い付いたのが、次の方法です。
既に、三角面を作る頂点の組み合わせは分かっています。
面を作らずに、クリックした位置が含まれる3個の頂点の組み合わせを
見つける事が出来れば、作成する面の数を最小限(通常は1個)に
すること出来そうです。
その為のアルゴリズムを探した所、こちらが見つかりました。
点と三角形の当たり判定( 内外判定 )
昔から色々と教わっているサイトです。
6個のベクトルを作成し、外積3回、内積2回で判断出来るようです。
で、作成したものがこちらです。
# Fusion360API Python script import traceback import adsk import adsk.core as core import adsk.fusion as fusion _app: core.Application = None _ui: core.UserInterface = None _handlers = [] CMD_INFO = { 'id': 'kantoku_test', 'name': 'test', 'tooltip': 'test' } _meshIpt: core.SelectionCommandInput = None class MyCommandCreatedHandler(core.CommandCreatedEventHandler): def __init__(self): super().__init__() def notify(self, args: core.CommandCreatedEventArgs): try: global _handlers cmd: core.Command = core.Command.cast(args.command) inputs: core.CommandInputs = cmd.commandInputs onDestroy = MyCommandDestroyHandler() cmd.destroy.add(onDestroy) _handlers.append(onDestroy) onExecutePreview = MyExecutePreviewHandler() cmd.executePreview.add(onExecutePreview) _handlers.append(onExecutePreview) global _meshIpt _meshIpt = inputs.addSelectionInput( '_meshIptId', 'mesh', 'Select Mesh', ) _meshIpt.addSelectionFilter(core.SelectionCommandInput.MeshBodies) except: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) class MyExecutePreviewHandler(core.CommandEventHandler): def __init__(self): super().__init__() def notify(self, args: core.CommandEventArgs): sel: core.Selection = _meshIpt.selection(0) meshBody: fusion.MeshBody = sel.entity point: core.Point3D = sel.point _meshIpt.clearSelection() meshBody.opacity = 0.5 cgBodies = get_triangle_bodies(meshBody, point) [_meshIpt.addSelection(b) for b in cgBodies] class MyCommandDestroyHandler(core.CommandEventHandler): def __init__(self): super().__init__() def notify(self, args: core.CommandEventArgs): adsk.terminate() def get_triangle_bodies( mesh: fusion.MeshBody, point: core.Point3D, ) -> list[fusion.BRepBody]: tmpMgr: fusion.TemporaryBRepManager = fusion.TemporaryBRepManager.get() # ******* def init_lines( pnts: list[core.Point3D], ) -> list[core.Line3D]: points = list(pnts) points.append(pnts[0]) lineLst = [] for p1, p2 in zip(points, points[1:]): try: line = core.Line3D.create(p1, p2) lineLst.append(line) except: pass return lineLst def init_pointSets( idxs: list, nodePoints: list[core.Point3D], ) -> tuple: return tuple([nodePoints[i] for i in idxs]) def init_wireBodies( crvs: list, ) -> list[fusion.BRepBody]: try: wireBody, _ = tmpMgr.createWireFromCurves(crvs) return wireBody except: pass def init_tri_faceBodies( wireBodies: list[fusion.BRepBody], ) -> list[fusion.BRepBody]: triBodies = [] for b in wireBodies: try: triBodies.append(tmpMgr.createFaceFromPlanarWires([b])) except: pass return triBodies def is_inner( point: core.Point3D, pointSet: tuple[core.Point3D], ) -> bool: vecAB: core.Vector3D = pointSet[1].vectorTo(pointSet[0]) vecBP: core.Vector3D = point.vectorTo(pointSet[1]) vecBC: core.Vector3D = pointSet[2].vectorTo(pointSet[1]) vecCP: core.Vector3D = point.vectorTo(pointSet[2]) vecCA: core.Vector3D = pointSet[0].vectorTo(pointSet[2]) vecAP: core.Vector3D = point.vectorTo(pointSet[0]) vec1: core.Vector3D = vecAB.crossProduct(vecBP) vec2: core.Vector3D = vecBC.crossProduct(vecCP) vec3: core.Vector3D = vecCA.crossProduct(vecAP) dot12: float = vec1.dotProduct(vec2) dot13: float = vec1.dotProduct(vec3) return True if dot12 > 0 and dot13 > 0 else False def draw_cg_bodies( comp: fusion.Component, bodyLst: list, ) -> list: cgGroup: fusion.CustomGraphicsGroup = comp.customGraphicsGroups.add() cgBodies = [cgGroup.addBRepBody(b) for b in bodyLst] blue = fusion.CustomGraphicsSolidColorEffect.create( core.Color.create(0,0,120,255) ) for b in cgBodies: b.color = blue return cgBodies # ******* triMesh: fusion.TriangleMesh = mesh.displayMesh nodeIndices = triMesh.nodeIndices triIndices = [nodeIndices[i: i + 3] for i in range(0, len(nodeIndices), 3)] nodePoints = [p for p in triMesh.nodeCoordinates] pointSets = [init_pointSets(idxs, nodePoints) for idxs in triIndices] triPointSets = [pnts for pnts in pointSets if is_inner(point, pnts)] if len(triPointSets) < 1: return lineSets = [init_lines(triPointSet) for triPointSet in triPointSets] triWires = [init_wireBodies(lines) for lines in lineSets] triBodies = init_tri_faceBodies(triWires) cgBodies = draw_cg_bodies(mesh.parentComponent, triBodies) return cgBodies def run(context): try: global _app, _ui _app = core.Application.get() _ui = _app.userInterface cmdDef: core.CommandDefinition = _ui.commandDefinitions.itemById( CMD_INFO['id'] ) if not cmdDef: cmdDef = _ui.commandDefinitions.addButtonDefinition( CMD_INFO['id'], CMD_INFO['name'], CMD_INFO['tooltip'] ) 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()))
選択した三角面と同じ位置にカスタムグラフィックの三角面を作って
表示させています。
長ったらしいですが、実際に処理させているのはget_triangle_bodies関数です。
しかし、問題が有りました。
最初に選択しているものは球体から作ったMeshBodyです。
こちらは狙った通りの面が選択出来ています。
二個目に選択しているものは、円柱から作ったMeshBodyです。
こちらは予想を裏切り、全く異なる面を選択してしまっています。
この二つの大きな違いは、MeshBodyが持っているメッシュグループが
単体か複数かと言う事に気が付くまで時間がかかりました。
最後に円柱のMeshBodyを一つのメッシュグループにし、その後
スクリプトを実行すると狙った動作となります。
何とも中途半端ですな。