Community
Fusion API and Scripts
Got a new add-in to share? Need something specialized to be scripted? Ask questions or share what you’ve discovered with the community.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Silhouette of a mesh body

5 REPLIES 5
SOLVED
Reply
Message 1 of 6
Joshua.mursic
344 Views, 5 Replies

Silhouette of a mesh body

I have a problem I have been trying to find a solution to for a while now. Is there a way to calculate the silhouette of a mesh body? Fusion is able to calculate the silhouette in the CAM environment, I am just wondering if their is a method to get a mesh silhouette as a sketch.

 

Edit: I have tried many things, but I think what I need to calculate is the concave hull. I have gotten this far but I am struggling with how to calculate the point with the greatest angle.

 

 

def getConcaveHull():
    design:adsk.fusion.Design = app.activeProduct
    root = design.rootComponent
    newSketch:adsk.fusion.Sketch = root.sketches.add(root.xYConstructionPlane)
    closestNPoints = 20

    mesh = root.meshBodies.item(0)
    nodes = mesh.displayMesh.nodeCoordinates
    yMin = None
    yIndex = None
    pList = []
    
    for i, node in enumerate(nodes):
        pList.append([node.x,node.y,node.z])
        if yMin == None or yMin.y > node.y:
            yMin = node
            yIndex = i
    #remove first point from list
    pList.pop(yIndex)
    #set current point to the yMin point
    currentPoint = adsk.core.Point3D.create(yMin.x,yMin.y,yMin.z)
    previousPoint = adsk.core.Point3D.create(yMin.x,yMin.y-0.1,yMin.z)

    for i in range(20):
        #sort list by distance to current point
        sortedList = sorted(pList,key=lambda node: adsk.core.Point3D.create(node[0],node[1],node[2]).distanceTo(currentPoint))
        #get the Nth closes points
        closestPoints = sortedList[:closestNPoints]
        #filter closestPoints for ccw angle points (these are points to the left of the current point)
        leftPoints = []
        for checkPoint in closestPoints:
            angle = (previousPoint.x-currentPoint.x)*(checkPoint[1]-currentPoint.y)-(checkPoint[0]-currentPoint.x)*(previousPoint.y-currentPoint.y)
            if angle <= 0:
                leftPoints.append([checkPoint,angle])
        #sort ccwPoints for largest angle between the previous 2 points (not getting the largest angle)
        sortedAngles = sorted(leftPoints,key=lambda ang: ang[1],reverse=False)
        #remove the largest angle point draw the line and make it the next point
        pList.remove([sortedAngles[0][0][0],sortedAngles[0][0][1],sortedAngles[0][0][2]])
        nextPoint = adsk.core.Point3D.create(sortedAngles[0][0][0],sortedAngles[0][0][1],sortedAngles[0][0][2])
        newSketch.sketchCurves.sketchLines.addByTwoPoints(currentPoint,nextPoint)
        currentPoint = nextPoint

 

 

It seems to be working until after line 35. I am sorting the closest 20 points to the current point and filtering out only the points to the left of that point. Now I am trying to find which one will create the largest angle, and thus the profile of the mesh.

 

Joshuamursic_0-1683730174437.png

Joshuamursic_1-1683730292859.png

 

you can see that it doesn't always get the point that is closest to the outside

 

I am ultimately trying to get this line. or as close as possible to it.

Joshuamursic_2-1683730386961.png

 

 

I have adapted some code I found online to make this, which calculates the convex hull.

 

def getConvexHull():
    design:adsk.fusion.Design = app.activeProduct
    root = design.rootComponent
    features = root.features

    def convexHull(pList: list) -> list:
        #define resulting conver hull list
        convexHullList = []

        #find left-most and right-most points and add to result
        leftPoint, rightPoint = min(pList), max(pList)
        convexHullList.append(leftPoint)
        convexHullList.append(rightPoint)

        #call upperHull algorithm for upper part of convex hull
        allPointsUpper = upperHull(leftPoint, rightPoint, pList)
        #call upperHull algorithm for lower part of convex hull (lower hull)
        allPointsLower = upperHull(rightPoint, leftPoint, pList)

        convexHullList += allPointsUpper
        convexHullList += allPointsLower

        return convexHullList

    def upperHull(a: list, b: list, pList: list):
        
        #base case for when there are no points to the left of selected vector
        if len(pList) == 0:
            return []

        upperHullPoints = []
        resultPoints = []
        #find p farthest from the line
        maxDis = 0.0
        furthestPoint = []
        for p in pList:
            if isLeft(a, b, p) == True:
                upperHullPoints.append(p)
                pDis = findDistance(a, b, p)
                if(pDis > maxDis):
                    maxDis = pDis
                    furthestPoint = p

        #add the furthest point to convexHull result (finalList)
        
        if furthestPoint:
            resultPoints.append(furthestPoint)

        #calling upperHull algorithm on region 1 (left of vector a, furthestPoint) and region 3 (left of vector furthestPoint, b)
        region1 = upperHull(a, furthestPoint, upperHullPoints)
        region3 = upperHull(furthestPoint, b, upperHullPoints)
        resultPoints += region1
        resultPoints += region3
        return resultPoints

    # Geometric Calculation Functions
    def findDistance(a: list, b: list, p: list):
        #rewriting coordinates for simply geometric syntax
        ax, ay, bx, by = a[0], a[1], b[0], b[1]
        px, py = p[0], p[1]
        d = 0
        d = (abs(((bx - ax) * (ay - py)) - ((ax - px) * (by - ay)))) / math.sqrt((pow((bx - ax), 2)) + (pow((by - ay), 2)))
        return d

    def isLeft(a: list, b: list, c: list) -> bool:
        #rewriting coordinates for simply geometric syntax
        ax, ay, bx, by, cx, cy = a[0], a[1], b[0], b[1], c[0], c[1]

        #we will take point a and point b and do the cross product of these points
        z = ((bx - ax) * (cy - ay)) - ((cx - ax) * (by - ay))

        if z > 0:
            return True
        else:
            return False

    newSketch:adsk.fusion.Sketch = root.sketches.add(root.xYConstructionPlane)
    mesh = root.meshBodies.item(0)
    nodes = mesh.displayMesh.nodeCoordinates
    pList = []
    for node in nodes:
        pList.append([node.x,node.y])

    points = convexHull(pList)
    for i in range(len(points)):
        newPoint = adsk.core.Point3D.create(points[i][0],points[i][1],0.0)
        newSketch.sketchPoints.add(newPoint)

 

 

but I am looking for the concave hull of the mesh.

5 REPLIES 5
Message 2 of 6
kandennti
in reply to: Joshua.mursic

Hi @Joshua.mursic . 

 

It seemed like an interesting theme, so we gave it a try.
Other people on the Japanese forum gave me some ideas, but I decided to do it the steady way.

The following script creates a silhouette surface in the XY plane of the specified MeshBody component.
I believe a sketch can be created from this surface.

 

# Fusion360API Python script

import traceback
import adsk.core as core
import adsk.fusion as fusion

def run(context):
    ui: core.UserInterface = None
    try:
        app: core.Application = core.Application.get()
        ui = app.userInterface

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

        create_polygon_silhouette(sel.entity)

        ui.messageBox('Done')

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


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

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


def create_polygon_silhouette(
    mesh: fusion.MeshBody
) -> list[fusion.BRepBody]:

    tmpMgr: fusion.TemporaryBRepManager = fusion.TemporaryBRepManager.get()

    # *******
    def z_reset(
        p: core.Point3D,
    ) -> core.Point3D:

        p.z = 0
        return p

    def init_lines(
        idxs: list,
        nodePoints: list[core.Point3D],
    ) -> list[core.Line3D]:

        idxs = list(idxs)
        idxs.append(idxs[0])
        
        lineLst = []
        for i1, i2 in zip(idxs, idxs[1:]):
            try:
                line = core.Line3D.create(nodePoints[i1], nodePoints[i2])
                lineLst.append(line)
            except:
                pass

        return lineLst

    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 triWires:
            try:
                triBodies.append(tmpMgr.createFaceFromPlanarWires([b]))
            except:
                pass

        return triBodies

    def unionBodies(
        bodies: list[fusion.BRepBody],
    ) -> fusion.BRepBody:

        def union(
            target: fusion.BRepBody,
            tool: fusion.BRepBody,
        ) -> None:

            try:
                tmpMgr.booleanOperation(
                    target,
                    tool,
                    fusion.BooleanTypes.UnionBooleanType)
            except:
                pass

        body: fusion.BRepBody = bodies.pop()
        [union(body, b) for b in bodies]

        return body

    def dump_Bodies(
        comp: fusion.Component,
        bodyLst: list,
        name: str = '',
    ) -> list[fusion.BRepBody]:

        des: fusion.Design = comp.parentDesign

        baseFeat: fusion.BaseFeature = None
        if des.designType == fusion.DesignTypes.ParametricDesignType:
            baseFeat = comp.features.baseFeatures.add()

        bodies: fusion.BRepBodies = comp.bRepBodies
        resBodies = []
        if baseFeat:
            try:
                baseFeat.startEdit()
                resBodies = [bodies.add(body, baseFeat) for body in bodyLst]
            except:
                pass
            finally:
                baseFeat.finishEdit()
        else:
            resBodies = [bodies.add(body) for body in bodyLst]

        if len(name) > 0:
            for b in resBodies:
                b.name = name

        return resBodies

    # *******
    triMesh: fusion.TriangleMesh = mesh.displayMesh

    nodeIndices = triMesh.nodeIndices
    triIndices = [nodeIndices[i: i + 3] for i in range(0, len(nodeIndices), 3)]

    nodePoints = [z_reset(p) for p in triMesh.nodeCoordinates]

    lineSets = [init_lines(idxs, nodePoints) for idxs in triIndices]

    triWires = [init_wireBodies(lines) for lines in lineSets]

    triBodies = init_tri_faceBodies(triWires)

    body: fusion.BRepBody = unionBodies(triBodies)

    return dump_Bodies(mesh.parentComponent, [body])

 


Please try with a small number of surfaces first, as processing is slow.
In some cases, an error may occur.


If you get an error, please attach the f3d file and we will work on it some more.

Message 3 of 6
Joshua.mursic
in reply to: kandennti

This works great for meshes with a small number of surfaces, thank you very much @kandennti 

Message 4 of 6
Joshua.mursic
in reply to: kandennti

Hello @kandennti, I am wondering if you can help me. I am using this function to try and Get the silhouette of a mesh. I would like to expand it to be able to get the silhouette in the context of the world instead of the components matrix.

 

Joshuamursic_1-1706211583171.png

right now the function works well and gets the silhouette in the context of the component. Is there any way to get the silhouette that would be formed if we are looing at the part along the world Z axis? Something like this:

Joshuamursic_2-1706211737737.png

 

I have tried to transform the points but they are incorrect. I have also attached a sample fusion project. 

import adsk.core, adsk.fusion, adsk.cam, traceback, os, sys
import math
try:
    import shapely
except:
    pass
    # os.system('cmd /c "' + f'{sys.path[0]}\Python\python.exe -m pip install shapely' + '"')
    # import shapely

import shapely.geometry
from shapely.geometry import mapping


def run(context):
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        design:adsk.fusion.Design = app.activeDocument.products.itemByProductType("DesignProductType")
        design.designType = adsk.fusion.DesignTypes.ParametricDesignType
        root = design.rootComponent
        occ = root.occurrences.item(0)
        getMeshConvexSilhouette(occ)

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


def getMeshConvexSilhouette(occurrence:adsk.fusion.Occurrence, offset=0.0):

    def graham_scan(points):
        """
        Compute the convex hull of a set of 2D points using the Graham's scan algorithm.

        Parameters:
        points (list): A list of 2D points. Each point is represented as a tuple of two numbers (x, y).

        Returns:
        list: The vertices of the convex hull in counterclockwise order.
        """

        # Function to check if the turn for three points is left or not
        def left_turn(p1, p2, p3):
            return (p2[0] - p1[0]) * (p3[1] - p1[1]) - (p2[1] - p1[1]) * (p3[0] - p1[0]) > 0

        # Step 1: Find the pivot, which is the point with the smallest y-coordinate.
        pivot = min(points, key=lambda point: (point[1], point[0]))
        points.remove(pivot)

        # Step 2: Sort the points counterclockwise by the angle they and the pivot make with the x-axis.
        points.sort(key=lambda point: (math.atan2(point[1] - pivot[1], point[0] - pivot[0]), (point[1] - pivot[1])**2 + (point[0] - pivot[0])**2))

        # Step 3: Initialize the stack with the first three points.
        stack = [pivot, points[0], points[1]]

        # Step 4: Process the remaining points.
        for point in points[2:]:
            while len(stack) > 1 and not left_turn(stack[-2], stack[-1], point):
                stack.pop()
            stack.append(point)

        return stack

    #calculate the boundary points using the transformation matrix so points are calculated with correct position?
    mesh = occurrence.component.meshBodies.item(0)
    component = occurrence.component
    nodes = mesh.displayMesh.nodeCoordinates
    points = []
    zTotal = 0.0
    for node in nodes:
        # node.transformBy(xxxx)
        points.append([node.x,node.y])
        zTotal+=node.z
    averageZ = zTotal/len(nodes)

    # Takes the convex hull, returning just the edge points in order
    hullPoints = graham_scan(points)
    # Simplify the boundary
    simplification=0.0001
    simplifiedPolygon = shapely.geometry.polygon.Polygon(hullPoints).simplify(simplification)

    if offset != 0.0:
        simplifiedPolygon = simplifiedPolygon.buffer(offset/10)
    mappedPolygon = mapping((simplifiedPolygon))
    coordinates = mappedPolygon['coordinates'][0]

    sketch = component.sketches.add(component.xYConstructionPlane)
    sketch.name = "ConvexBoundary"
    sketch.isComputeDeferred = True
    sketch.arePointsShown = False
    splinePoints = adsk.core.ObjectCollection.create()
    for i in range(0,len(coordinates)):
        splinePoint = adsk.core.Point3D.create(coordinates[i][0],coordinates[i][1],averageZ)
        # splinePoint.transformBy(xxxx)
        splinePoints.add(splinePoint)
    sketch.sketchCurves.sketchFittedSplines.add(splinePoints)

    sketch.isComputeDeferred = False

 

 

Message 5 of 6
kandennti
in reply to: Joshua.mursic

Hi @Joshua.mursic -San.

 

I am sorry, but I have never used "shapely" before, so I do not know the details.

 

On a personal note, I have a new job this year, so I don't have as much time to work on the Fusion360API as I used to.
I am sure that once I get used to my new job, I will be able to find some time to work on it, but I am not at that stage yet.

Message 6 of 6
jordan783ZS
in reply to: kandennti

Hi @kandennti , curious how hard it would be to adapt this script to work on any kind of body or even a component?
We often have to create the silhouette of a model to determine surface area for wind loading calculations, and this is currently done completely manually. A script like this would likely massively speed up the process.

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Autodesk Design & Make Report