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: 

Convert multiple lines into a single spline

11 REPLIES 11
SOLVED
Reply
Message 1 of 12
pl.fauny
2515 Views, 11 Replies

Convert multiple lines into a single spline

Hi,

I'm looking for a way to create a single spline from multiple connected lines.

When I create a spiral in 2D and them add extra lines connected together through the end points, I get multiples selections :

pierrelouisfauny_0-1626705456486.png

For the need of an other software, I need to join the spiral, the fillet and the straight line as a single selection.

The final output is a DXF.

I saw an add-on that can fragment a spline into multiple lines but not the opposite.

https://apps.autodesk.com/FUSION/en/Detail/Index?id=4611814297957846949

 

Any idea to do this in a simple way?

 

Thanks

 

11 REPLIES 11
Message 2 of 12
kandennti
in reply to: pl.fauny

Hi @pl.fauny .

 

I had a bit of a challenge.

When you select a line in the sketch after running the script, a new sketch is created and a single spline is created.

 

# Fusion360API Python script
import adsk.core, adsk.fusion, traceback

def run(context):
    ui = adsk.core.UserInterface.cast(None)
    try:
        tolerance = 0.1

        app :adsk.fusion.Application = adsk.core.Application.get()
        ui = app.userInterface

        # select sketch curve
        msg :str = 'Select'
        selFiltter :str = 'SketchCurves'
        sel :adsk.core.Selection = selectEnt(msg ,selFiltter)
        if not sel: return

        # get sketch curve
        sktCrv :adsk.fusion.SketchEntity = sel.entity
        refSkt: adsk.fusion.Sketch = sktCrv.parentSketch

        # get path
        comp: adsk.fusion.Component = refSkt.parentComponent
        path: adsk.fusion.Path = comp.features.createPath(sktCrv)
        if path.count < 2:
            return

        # get points
        pathEnt: adsk.fusion.PathEntity
        pntsGroup = []
        for pathEnt in path:
            eva: adsk.core.CurveEvaluator3D = pathEnt.entity.geometry.evaluator
            _, startPrm, endPrm = eva.getParameterExtents()
            _, vertexCoordinates = eva.getStrokes(startPrm, endPrm, tolerance)
            pntsGroup.append(vertexCoordinates)

        # Reordering
        objs: adsk.core.ObjectCollection = adsk.core.ObjectCollection.create()
        if pntsGroup[0][0].isEqualTo(pntsGroup[1][0]) or pntsGroup[0][0].isEqualTo(pntsGroup[1][-1]):
            [objs.add(p) for p in pntsGroup[0][::-1]]
        else:
            [objs.add(p) for p in pntsGroup[0]]

        for pnts in pntsGroup[1:]:
            pnt: adsk.core.Point3D = objs.item(objs.count - 1)
            if pnt.isEqualTo(pnts[0]):
                [objs.add(p) for p in pnts[1:]]
            else:
                [objs.add(p) for p in pnts[1::-1]]

        # create curve
        skt: adsk.fusion.Sketch = comp.sketches.add(refSkt.referencePlane)
        skt.arePointsShown = False
        skt.isComputeDeferred = True
        skt.sketchCurves.sketchFittedSplines.add(objs)
        skt.isComputeDeferred = False

        ui.messageBox('Done')

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

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

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

 

 

Tolerance is a big factor, and the finer you make it, the longer the processing time will be.

1.png

 

If you understand NurbsCurve, you may be able to create something more exact, but I don't have the knowledge.

Message 3 of 12
pl.fauny
in reply to: pl.fauny

Hi @kandennti ,

 

Thank for your help and your time.

I made your scrypt works with your file curve.f3d.

But with my file (spiral.f3d), I've got a strange behaviour :

plfauny_0-1626786536728.png

I use different way to make the spiral. Maybe that why it's not working on mine. I use a tolerance variable of 0.05.

 

If you have any idea...

 

Thanks

Message 4 of 12
kandennti
in reply to: pl.fauny

@pl.fauny .

 

It now works correctly with "Spiral.f3d".
Also, the method of obtaining points has been slightly changed.


However, the accuracy of reproduction is still low.

# Fusion360API Python script
import adsk.core, adsk.fusion, traceback

def run(context):
    ui = adsk.core.UserInterface.cast(None)
    try:
        tolerance = 0.01

        app :adsk.fusion.Application = adsk.core.Application.get()
        ui = app.userInterface

        # select sketch curve
        msg :str = 'Select'
        selFiltter :str = 'SketchCurves'
        sel :adsk.core.Selection = selectEnt(msg ,selFiltter)
        if not sel: return

        # get sketch curve
        sktCrv :adsk.fusion.SketchEntity = sel.entity
        refSkt: adsk.fusion.Sketch = sktCrv.parentSketch

        # get path
        comp: adsk.fusion.Component = refSkt.parentComponent
        path: adsk.fusion.Path = comp.features.createPath(sktCrv)
        if path.count < 2:
            return

        # get points
        pathEnt: adsk.fusion.PathEntity
        pntsGroup = []
        for pathEnt in path:
            geo = pathEnt.entity.worldGeometry
            if hasattr(geo, 'asNurbsCurve'):
                geo = geo.asNurbsCurve
            eva: adsk.core.CurveEvaluator3D = geo.evaluator
            _, startPrm, endPrm = eva.getParameterExtents()
            _, vertexCoordinates = eva.getStrokes(startPrm, endPrm, tolerance)

            nearStart = getNearPoint(eva,vertexCoordinates[0],vertexCoordinates[1])
            nearEnd = getNearPoint(eva,vertexCoordinates[-1],vertexCoordinates[-2])
            pnts = [p for p in vertexCoordinates]
            pnts.insert(1, nearStart)
            pnts.insert(-1, nearEnd)

            pntsGroup.append(pnts)

        # Reordering
        objs: adsk.core.ObjectCollection = adsk.core.ObjectCollection.create()
        if pntsGroup[0][0].isEqualTo(pntsGroup[1][0]) or pntsGroup[0][0].isEqualTo(pntsGroup[1][-1]):
            [objs.add(p) for p in pntsGroup[0][::-1]]
        else:
            [objs.add(p) for p in pntsGroup[0]]

        for pnts in pntsGroup[1:]:
            pnt: adsk.core.Point3D = objs.item(objs.count - 1)
            if pnt.isEqualTo(pnts[0]):
                [objs.add(p) for p in pnts[1:]]
            else:
                [objs.add(p) for p in pnts[1::-1]]

        # create curve
        skt: adsk.fusion.Sketch = comp.sketches.add(comp.xYConstructionPlane)
        skt.arePointsShown = False
        skt.isComputeDeferred = True
        skt.sketchCurves.sketchFittedSplines.add(objs)
        skt.isComputeDeferred = False

        ui.messageBox('Done')

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

def getNearPoint(
    eva: adsk.core.CurveEvaluator3D,
    p1: adsk.core.Point3D,
    p2: adsk.core.Point3D) -> adsk.core.Point3D:

    _, prms = eva.getParametersAtPoints([p1,p2])
    nearPrm = (prms[1] - prms[0]) * 0.1
    prm = prms[0] + nearPrm
    _, pnt = eva.getPointAtParameter(prm)

    return pnt

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

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

1.png

 

Message 5 of 12
pl.fauny
in reply to: pl.fauny

@kandennti,

 

Thank you for the update.

Unfortunatly I loose to much precision on this version around the fillet. If the fillet get bigger, it's just convert as a chamfer.

plfauny_1-1626793992724.png

 

This spline will become an electronic track in an other software, so I need to keep some accuracy.

In any case, you went much further than I could 😉 Thanks

Message 6 of 12
kandennti
in reply to: pl.fauny

@pl.fauny .

 

Initially, I used the CurveEvaluator3D.getStrokes method to get the passing points from each line and create a spline.

 

The second one was improved to add only one passing point near the start and end points of each line.

 

I also added more passing points near the start and end points.

 

# Fusion360API Python script
import adsk.core, adsk.fusion, traceback

_tolerance = 0.01
_nearCount = 5

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

        # select sketch curve
        msg :str = 'Select'
        selFiltter :str = 'SketchCurves'
        sel :adsk.core.Selection = selectEnt(msg ,selFiltter)
        if not sel: return

        # get sketch curve
        sktCrv :adsk.fusion.SketchEntity = sel.entity
        refSkt: adsk.fusion.Sketch = sktCrv.parentSketch

        # get path
        comp: adsk.fusion.Component = refSkt.parentComponent
        path: adsk.fusion.Path = comp.features.createPath(sktCrv)
        if path.count < 2:
            return

        # get points
        pathEnt: adsk.fusion.PathEntity
        pntsGroup = []
        for pathEnt in path:
            geo = pathEnt.entity.worldGeometry
            if hasattr(geo, 'asNurbsCurve'):
                geo = geo.asNurbsCurve

            eva: adsk.core.CurveEvaluator3D = geo.evaluator
            _, startPrm, endPrm = eva.getParameterExtents()
            _, vertexCoordinates = eva.getStrokes(startPrm, endPrm, _tolerance)
            pnts = [p for p in vertexCoordinates]
            pntsCount = len(pnts)
            print(f'-- {geo.objectType},{startPrm},{endPrm} --')
            nearStarts = getNearPoints(eva, _nearCount, pnts[0], pnts[1])
            pnts[1:1] = nearStarts

            if pntsCount > 2:
                nearEnds = getNearPoints(eva, _nearCount, pnts[-1], pnts[-2])
                pnts[-1:1] = nearEnds[::-1]

            pntsGroup.append(pnts)

        # Reordering
        objs: adsk.core.ObjectCollection = adsk.core.ObjectCollection.create()
        if pntsGroup[0][0].isEqualTo(pntsGroup[1][0]) or pntsGroup[0][0].isEqualTo(pntsGroup[1][-1]):
            [objs.add(p) for p in pntsGroup[0][::-1]]
        else:
            [objs.add(p) for p in pntsGroup[0]]

        for pnts in pntsGroup[1:]:
            pnt: adsk.core.Point3D = objs.item(objs.count - 1)
            if pnt.isEqualTo(pnts[0]):
                [objs.add(p) for p in pnts[1:]]
            else:
                [objs.add(p) for p in pnts[1::-1]]

        # create curve
        skt: adsk.fusion.Sketch = comp.sketches.add(comp.xYConstructionPlane)
        skt.arePointsShown = False # hide sketch points
        skt.isComputeDeferred = True
        skt.sketchCurves.sketchFittedSplines.add(objs)
        skt.isComputeDeferred = False

        ui.messageBox('Done')

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

def getNearPoints(
    eva: adsk.core.CurveEvaluator3D,
    count :int,
    p1: adsk.core.Point3D,
    p2: adsk.core.Point3D) -> list:

    _, prms = eva.getParametersAtPoints([p1,p2])
    nearPrm = (prms[1] - prms[0]) / (_nearCount + 1)
    prmLst = [prms[0] + (nearPrm * idx) for idx in range(1, count + 1)]
    print(f'{prms[0]}:{prms[1]}:{nearPrm}')

    _, tmpLst = eva.getPointsAtParameters(prmLst)
    pntLst = [p for p in tmpLst if not p1.isEqualTo(p)]
    print(f'count:{len(pntLst)}')

    return pntLst

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

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

 

1.png

It's a little better, but not much better.

 

I have also noticed that the arc part is not correct.

So I made a sample with only multiple arcs and tried it, and made it display the points as well.

1.png

The results were different depending on the arc selected.
I suspect that the cause of this is that the passage point is not calculated correctly due to the relationship between the start point and the end point, but I have no idea how to deal with this.

Message 7 of 12
pl.fauny
in reply to: pl.fauny

Hi @kandennti ,

I have no idea neither or the competences to deal with this problem.

In an other hand, I had the same problem with svg files that are makes with multiple selection lines, and your srcypt make a perfect feating job in this case.

Here a view of one letter from a SVG file where original svg lines in green and post processing line in blue are perfectly feating.

plfauny_0-1626852805164.png

So you made it works for a half of my problems 😉 Thank you.

 

Message 8 of 12
BrianEkins
in reply to: pl.fauny

Here's a little test script I wrote to see if I could get it to work.  It works on the geometry I created to test with.  To use it, select the sketch entities in the order they're connected and then run the script.  Of course, it can be easily edited to pass in a list of entities.  The result should be a single curve that is exactly the same shape as the original inputs curves.

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

        # Iterate through the selected curves and add them to a list.
        # It's expected that the curves were selected in their connected
        # order so not work is dont to make sure they're in order.
        curves = []
        for selection in ui.activeSelections:
            # Get each curve as a NURBS curve.
            nurbs: adsk.core.NurbsCurve3D = None
            sketchCurve: adsk.fusion.SketchCurve = selection.entity
            if sketchCurve.geometry.objectType == adsk.core.NurbsCurve3D.classType():
                nurbs = sketchCurve.geometry
            else:
                nurbs = sketchCurve.geometry.asNurbsCurve

            if len(curves) == 1:
                # Determine if the end of the start curve matches either the start
                # or end of the second curve. If not, reverse the start curve.
                firstCurve: adsk.core.NurbsCurve3D = curves[0]
                newCurve: adsk.core.NurbsCurve3D = nurbs

                firstStart: adsk.core.Point3D = firstCurve.controlPoints[0]
                firstEnd: adsk.core.Point3D = firstCurve.controlPoints[len(firstCurve.controlPoints) - 1]

                newStart: adsk.core.Point3D = newCurve.controlPoints[0]
                newEnd: adsk.core.Point3D = newCurve.controlPoints[len(newCurve.controlPoints) - 1]
                
                if firstStart.isEqualTo(newStart) or firstStart.isEqualTo(newEnd):
                    curves[0] = reverseNurbsCurve(curves[0])

            # For each subsequent curve, check to see that it's start matches the end of the previous
            # curve.  If it doesn't reverse the new curve.
            if len(curves) > 0:
                previousCurve: adsk.core.NurbsCurve3D = curves[len(curves) - 1]
                newCurve: adsk.core.NurbsCurve3D = nurbs

                existingEnd: adsk.core.Point3D = previousCurve.controlPoints[len(previousCurve.controlPoints) - 1]
                newStart: adsk.core.Point3D = newCurve.controlPoints[0]

                if not existingEnd.isEqualTo(newStart):
                    nurbs = reverseNurbsCurve(nurbs)

            # Add the curve to the list.
            curves.append(nurbs)

        # Merge the curves together to create a single NURBS curve.
        bigCurve: adsk.core.NurbsCurve3D = None
        for nurb in curves:
            if bigCurve is None:
                bigCurve = nurb
            else:
                bigCurve = bigCurve.merge(nurb)

        # To validate the result, create a new sketch and create a curve on it.
        # If the sketch containing the original geometry is on the X-Y root construcution
        # plane then the new geometry should exactly overlay the existing geometry.
        root: adsk.fusion.Component = app.activeProduct.rootComponent
        sk = root.sketches.add(root.xYConstructionPlane)
        sk.sketchCurves.sketchFixedSplines.addByNurbsCurve(bigCurve)
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

# Reverses and returns the input NURBS curve.
def reverseNurbsCurve(curve: adsk.core.NurbsCurve3D) -> adsk.core.NurbsCurve3D:
    (_, points, degree, knots, isRational, weights, isPeriodic) = curve.getData()
    # Reverse the points and weights.
    points = points[::-1]
    weights = weights[::-1]

    # Create and return a new curve created with the reversed data.
    if isRational:
        return adsk.core.NurbsCurve3D.createRational(points, degree, knots, weights, isPeriodic)
    else:
        return adsk.core.NurbsCurve3D.createNonRational(points, degree, knots, isPeriodic)

 

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
Message 9 of 12
pl.fauny
in reply to: pl.fauny

Hi @BrianEkins ,

It works very well, thank a lot !

One thing interesting is that make a 180° rotation.

plfauny_0-1627026833616.png

In the case where I'm working in a sub component, the new sketch is created in the main project.

But those are just small details 😉

 

Thanks both of you @kandennti and @BrianEkins for your work and your time to provide me this good solution !

 

Message 10 of 12
kandennti
in reply to: pl.fauny

@BrianEkins .

I overlooked the merge method.
I learned a lot from it.

 

@pl.fauny .

To make the orientation correct, please fix the following

・・・
            if sketchCurve.geometry.objectType == adsk.core.NurbsCurve3D.classType():
                # nurbs = sketchCurve.geometry
                nurbs = sketchCurve.worldGeometry
            else:
                # nurbs = sketchCurve.geometry.asNurbsCurve
                nurbs = sketchCurve.worldGeometry.asNurbsCurve
・・・
Message 11 of 12
BrianEkins
in reply to: pl.fauny

I'm glad it worked.  I think the reason for the change in orientation is because I'm drawing the new curve on a different sketch and it may be oriented differently than the original.  You can also change the code so the new curve is drawn in the same sketch as the original curves and then they should be the same.  

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
Message 12 of 12
pl.fauny
in reply to: pl.fauny

It works like a charm with the worldGeometry update !

Thanks @BrianEkins and @kandennti 🙂

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