Announcements
Attention for Customers without Multi-Factor Authentication or Single Sign-On - OTP Verification rolls out April 2025. Read all about it here.

Use Selection Input to select a single triangle in a mesh?

Joshua.mursic
Advocate

Use Selection Input to select a single triangle in a mesh?

Joshua.mursic
Advocate
Advocate

Is there a way to have the user select a single triangle in a meshbody? I am trying to add sprues to a mesh body in the location that the user wants. By selecting the mesh body I will build a cylinder that follows the normal of the triangle that is selected. I see that there is a way to select an entire mesh, but I don't see a single triangle selection option in the list of options. 

https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-03033DE6-AD8E-46B3-B4E6-DADA8D389E4E

 

meshTriangleSelection = inputs.addSelectionInput('meshTriangleSelection', 'Build Sprue', 'Select point')
meshTriangleSelection.addSelectionFilter('MeshBodies')
meshTriangleSelection.setSelectionLimits(0)

 

the mesh selection palette has an option to select a mesh face.

Joshuamursic_0-1684846141032.png

 

I am having a hard time finding a way to replicate this palettes functionality so that I can create a sprue on the surface where the user selects.

 

0 Likes
Reply
Accepted solutions (1)
771 Views
6 Replies
Replies (6)

kandennti
Mentor
Mentor

Hi @Joshua.mursic .

 

Perhaps the ability to select mesh faces is not provided in the API.

0 Likes

Joshua.mursic
Advocate
Advocate
Accepted solution

I came up with this as a solution:

def constructSupport():
    design:adsk.fusion.Design = app.activeProduct
    root = design.rootComponent
    design.designType = adsk.fusion.DesignTypes.DirectDesignType

    # select PlanarFaces
    try:
        selection :adsk.core.Selection = ui.selectEntity(
            'Select Face', 'MeshBodies')
    except:
        return

    # Click Point
    point :adsk.core.Point3D = selection.point
    mesh:adsk.fusion.MeshBody = selection.entity
    nodes = mesh.displayMesh.nodeCoordinates
    closePoint1 = nodes[0]
    closePoint2 = nodes[0]
    closePoint3 = nodes[0]
    for node in nodes:
        if point.distanceTo(node) < point.distanceTo(closePoint1):
            closePoint3 = closePoint2
            closePoint2 = closePoint1
            closePoint1 = node
    
    # Create Sketch Plane
    vec1 = adsk.core.Vector3D.create(closePoint1.x-closePoint2.x,closePoint1.y-closePoint2.y,closePoint1.z-closePoint2.z)
    vec2 = adsk.core.Vector3D.create(closePoint1.x-closePoint3.x,closePoint1.y-closePoint3.y,closePoint1.z-closePoint3.z)
    normalVec = vec1.crossProduct(vec2)
    plane = adsk.core.Plane.create(point,normalVec)

    planeInput:adsk.fusion.ConstructionPlaneInput = root.constructionPlanes.createInput()
    planeInput.setByPlane(plane)
    sketchPlane = root.constructionPlanes.add(planeInput)

    # Create Sketch
    newSketch:adsk.fusion.Sketch = root.sketches.add(sketchPlane)

    # Get the transformation Matrix for the sketch
    matrix :adsk.core.Matrix3D = newSketch.transform
    matrix.invert()
    point.transformBy(matrix)

    # Draw Sketch
    newSketch.sketchCurves.sketchCircles.addByCenterRadius(point,0.1)
2 Likes

Jorge_Jaramillo
Collaborator
Collaborator

Hi,

 

I can see there is a "ParaMesh.TriangleSelection"  text command, but I get the following error when I used it:

No active ParaMeshFacetSelection in SelectionSet

 

@kandennti: Do you know how to use it?

 

Regards,

Jorge Jaramillo

 

1 Like

kandennti
Mentor
Mentor

@Joshua.mursic .

 

I have created a sample script that makes the triangular faces of the mesh body appear to be selected, rather than actually selecting them.
What is actually displayed are custom graphics.

# 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()))

 

But there is a problem. Here is a video of the actual test.

 

The first MeshBody selected was created from a sphere.
This one shows the targeted triangular surface.
The next meshBody selected is created from a cylinder.
This one shows a completely different surface.

 

The difference between the two MeshBodies is whether the MeshBody has one or multiple Face Groups.

 

Finally, when I run the script after changing the Face Group of the cylinder's MeshBody to one, the targeted face is displayed.

 

 

My guess, since I haven't looked into it in detail, is that MeshBody.displayMesh is not returning all the information.

0 Likes

kandennti
Mentor
Mentor

@Jorge_Jaramillo .

 

'ParaMesh.TriangleSelection', I could not figure out how to use it.
'ParaMesh.SelectionPalette' in direct mode, I have confirmed that the "mesh selection palette" is displayed.

 

It may be possible to change the settings in the dialog, but I don't know how to select the triangles in the MeshBody.

 

 

The only text command I know of to select elements in CAD space is "Commands.Pick".

https://github.com/kantoku-code/Fusion360_Small_Tools_for_Developers/blob/master/TextCommands/TextCo... 

 

Create the appropriate body, press home on the view cube and execute the following text command

1.png

One surface has been selected.

 

Next, move the body to the edge of the screen and execute the same command, but nothing is selected.

1.png

 

I think <BRect> is not a value in 3DCAD space, but a value on the screen.
I haven't looked into it in detail or tried it, but the return value of the Viewport.viewToModelSpace method might be useful.

https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-277144B6-5E80-4D56-A31A-3DC1697893C5 

 

 

When I ran this text command on a MeshBody with the "mesh selection palette" displayed, I expected it to select the triangles, but it did not.
Therefore, I have determined that there is no way to select triangles at this time.

 

1 Like

Joshua.mursic
Advocate
Advocate

Thanks for all of the feedback it has been very helpful. I modified my original code to this: 

 

def constructSupport():
    design:adsk.fusion.Design = app.activeProduct
    root = design.rootComponent
    features = root.features
    extrudes = features.extrudeFeatures
    design.designType = adsk.fusion.DesignTypes.DirectDesignType

    # select PlanarFaces
    try:
        selection :adsk.core.Selection = ui.selectEntity(
            'Select Face', 'MeshBodies')
    except:
        return

    # Click Point
    point :adsk.core.Point3D = selection.point
    mesh:adsk.fusion.MeshBody = selection.entity
    nodes = mesh.displayMesh.nodeCoordinates
    distanceArray = []
    for node in nodes:
        distance = point.distanceTo(node)
        distanceArray.append([distance,node])
    
    sortedDistance = sorted(distanceArray,key=lambda dis: dis[0])
    point1 = sortedDistance[0][1]
    point2 = sortedDistance[1][1]
    point3 = sortedDistance[2][1]
    
    # Create Sketch Plane
    vec1 = adsk.core.Vector3D.create(point1.x-point2.x,point1.y-point2.y,point1.z-point2.z)
    vec2 = adsk.core.Vector3D.create(point1.x-point3.x,point1.y-point3.y,point1.z-point3.z)
    normalVec = vec1.crossProduct(vec2)
    plane = adsk.core.Plane.create(point,normalVec)
    planeInput:adsk.fusion.ConstructionPlaneInput = root.constructionPlanes.createInput()
    planeInput.setByPlane(plane)
    sketchPlane = root.constructionPlanes.add(planeInput)

    # Create Sketch
    newSketch:adsk.fusion.Sketch = root.sketches.add(sketchPlane)
    # Get the transformation Matrix for the sketch
    matrix :adsk.core.Matrix3D = newSketch.transform
    matrix.invert()
    point.transformBy(matrix)
    # Draw Sketch
    newSketch.sketchCurves.sketchCircles.addByCenterRadius(point,0.1)

    #Draw close points
    if True:
        newSketch.sketchPoints.add(point1)
        newSketch.sketchPoints.add(point2)
        newSketch.sketchPoints.add(point3)
        newSketch.sketchPoints.add(point)

    # Creates Extrude
    extrudeDistance = adsk.fusion.DistanceExtentDefinition.create(adsk.core.ValueInput.createByReal(-0.4))
    extrudeInput = extrudes.createInput(newSketch.profiles[0], adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
    extrudeInput.setOneSideExtent(extrudeDistance,adsk.fusion.ExtentDirections.PositiveExtentDirection)
    extrudeInput.isSolid = True
    extrudes.add(extrudeInput)
    #Delete Sketch
    newSketch.deleteMe()
    sketchPlane.deleteMe()

 

This is working well to pick a triangle. However I am having a hard time making sure the sprue is facing the correct direction. 

 

Joshuamursic_0-1685450539254.png

 

you can see in the image, sometimes the sprue is facing the inside of the mesh instead of outside. Any ideas on how to ensure that the sprue direction vector is in the positive direction?

 

0 Likes