Howto grafully handle creating a loft that would intersect itself - and fix it.

Howto grafully handle creating a loft that would intersect itself - and fix it.

wh6Q9NU
Advocate Advocate
1,334 Views
7 Replies
Message 1 of 8

Howto grafully handle creating a loft that would intersect itself - and fix it.

wh6Q9NU
Advocate
Advocate

Thanks to this example I'm able to create lofts easily: 

Loft Feature API Sample API Sample, see https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-a03e26b4-4a3c-11e6-a2e4-3417ebd41e19

 

Sometimes when I add a profile to a loftSections object I get an error that excepts my Phyton script (in a try: ... except).

When I repeat the step in the UI, I get the warning:

 

'Warning: The loft would intersect itself. Try changing the inputs to avoid this. If you wish to build a self-intersecting shape, try building the shape using multiple lofts.Warning: The object that you are creating is not visible. Please toggle visibility in the browser.'

 

 

Question: is there a way to gracefully intercept these warnings, so I can discard the profile just added and continue building the loft with the other profiles available?

 

I tried using "loftSections.isValid" or "profile.deleteMe", but none would prevent my Python script from throwing an exception.

 

Thanks!

 

Screenshot 2020-01-06 at 20.39.08.png

0 Likes
Accepted solutions (1)
1,335 Views
7 Replies
Replies (7)
Message 2 of 8

kandennti
Mentor
Mentor

Hi @wh6Q9NU .

 

I'm interested.

Is it possible to attach an archive that causes an error?

0 Likes
Message 3 of 8

wh6Q9NU
Advocate
Advocate

I'll share here a collection of functions and code that will create the loft(s). On line 384 one can choose to enable the first codition (if i > 1 and i < 8:) and the script will not throw.

 

Question is: how can a loft be checked in advance (by script, to prevent a throw) so invalid profiles can be skipped or lofts can be created in smaller segments as the script error suggests.

 

NB: the example script enforces the error of a loft intersecting itself and it would be easy to generate profiles at a greater distance between each other. But sometimes the loft created has NO overlapping profiles and still throws an error because the loft created is intersecting itself, or for other reasons (like 'profile can not be tangent to profile curve' or a somewhat similar error message).

 

Example dynamic loft creating script:

import adsk.core, adsk.fusion, traceback
import json
import math

def run(context):
    ui = None

    # points as inline json string
    points = '[{"x":0,"y":5.073,"z":0},{"x":0.009,"y":5.073,"z":0},{"x":0.019,"y":5.072,"z":0},{"x":0.062,"y":5.063,"z":0},{"x":0.11,"y":5.043,"z":0},{"x":0.219,"y":4.985,"z":0},{"x":0.378,"y":4.9,"z":0},{"x":0.865,"y":4.631,"z":0},{"x":2.398,"y":3.032,"z":0},{"x":2.813,"y":2.278,"z":0},{"x":3.056,"y":1.668,"z":0},{"x":3.267,"y":1.139,"z":0},{"x":3.406,"y":0.571,"z":0}]'

    # curves 7 profiles as inline json string
    curvesProfiles = '[{"logs":[],"curves":[{"controlpoints":[{"x":-17.164698747883282,"y":17.983441575918768},{"x":-19.276646783197553,"y":15.072531043913877},{"x":-19.842079309499557,"y":9.943955465135005},{"x":-16.167172213186305,"y":7.92705030228685}]},{"controlpoints":[{"x":-16.167172213186305,"y":7.92705030228685},{"x":-14.928784783562827,"y":7.247384140679769},{"x":-13.064351705933902,"y":7.180103779376454},{"x":-12.175987900344557,"y":8.469282960447748}]},{"controlpoints":[{"x":-12.175987900344557,"y":8.469282960447748},{"x":-11.793612183755853,"y":9.02418041357659},{"x":-11.65497624954449,"y":10.174654013358671},{"x":-12.497151171652042,"y":10.414568937788822}]},{"controlpoints":[{"x":-12.497151171652042,"y":10.414568937788822},{"x":-12.732758801110624,"y":10.481687757327245},{"x":-13.230486534171773,"y":10.319471218563942},{"x":-13.00189201028427,"y":10.003965490376801}]}],"profiles":[{"time":0,"breakTime":0.9668013116203719,"flip":-1,"curvature":-0.006582480615909264,"x":-17.164698747883282,"y":17.983441575918768,"normalX":-16.75999515809407,"normalY":17.689817659297987},{"time":0.12397340822138267,"breakTime":0.9668013116203719,"flip":-1,"curvature":-0.005316211641262808,"x":-18.261255115729526,"y":15.917366583246196,"normalX":-17.792936891358234,"normalY":15.742215103886977},{"time":0.3639686579083045,"breakTime":0.9668013116203719,"flip":-1,"curvature":-0.007973773790285842,"x":-18.705118373189233,"y":11.454742650640483,"normalX":-18.215106175664925,"normalY":11.554181306491191},{"time":0.6270766760372518,"breakTime":0.9668013116203719,"flip":-1,"curvature":-0.01268499359892902,"x":-15.727121422657488,"y":7.72173654893293,"normalX":-15.544793375893343,"normalY":8.187307685670564},{"time":0.8299716224098505,"breakTime":0.9668013116203719,"flip":-1,"curvature":-0.021737997980057003,"x":-12.220067619140753,"y":8.407499232010926,"normalX":-12.622273198607513,"normalY":8.704535714359151},{"time":0.9376861973507108,"breakTime":0.9668013116203719,"flip":-1,"curvature":-0.06545774344173169,"x":-12.210696540609026,"y":10.273496678370055,"normalX":-12.515329568971481,"normalY":9.877013743011268},{"time":0.9668013116203719,"breakTime":0.9668013116203719,"flip":-1,"curvature":-0.07200862608751893,"x":-12.725072984595046,"y":10.420408493599918,"normalX":-12.642924838193753,"normalY":9.927202976555723},{"time":0.9751009837152789,"breakTime":0.9668013116203719,"flip":-1,"curvature":-0.09190712828187159,"x":-12.875537879144028,"y":10.376731162758054,"normalX":-12.676150854235896,"normalY":9.918206556768055},{"time":0.9834006558101859,"breakTime":0.9668013116203719,"flip":-1,"curvature":-0.19038649718841621,"x":-13.00509802809042,"y":10.289876552236755,"normalX":-12.63890448608533,"normalY":9.94943229865951},{"time":0.9917003279050929,"breakTime":0.9668013116203719,"flip":-1,"curvature":-0.2657275421001583,"x":-13.06018020708229,"y":10.14754961150576,"normalX":-12.561040502134478,"normalY":10.176867778989628},{"time":0.9999999999999999,"breakTime":0.9668013116203719,"flip":-1,"curvature":-0.10940553166634062,"x":-13.00189201028427,"y":10.003965490376801,"normalX":-12.596997080386581,"normalY":10.297325500847123}]}]'

    def createExtrude(rootComp, design, pointData, flipX):
        SX = 0.1

        # Create a new sketch on the xy plane.
        sketch = rootComp.sketches.add(rootComp.xYConstructionPlane)

        # Create an object collection for the points.
        points = adsk.core.ObjectCollection.create()

        # Define the points the spline with fit through.
        for i in range(len(pointData)):
            points.add(adsk.core.Point3D.create(SX * flipX * pointData[i]['x'], SX * pointData[i]['y'], SX * pointData[i]['z']))

        for i in reversed(range(len(pointData))):
            points.add(adsk.core.Point3D.create(SX * flipX * pointData[i]['x'], -1 * SX * pointData[i]['y'], SX * pointData[i]['z']))

        points.add(adsk.core.Point3D.create(0, 0, 0))
        points.add(adsk.core.Point3D.create(SX * flipX * pointData[0]['x'], SX * pointData[0]['y'], SX * pointData[0]['z']))

        # Create the spline.
        sketch.sketchCurves.sketchFittedSplines.add(points)

        # Get the profile defined by the circle
        prof = sketch.profiles.item(0)

        # Extrude shortRight
        distance = adsk.core.ValueInput.createByReal(SX * -1)
        extrude1 = extrudes.addSimple(prof, distance, adsk.fusion.FeatureOperations.NewBodyFeatureOperation)        

        # Get the state of the extrusion
        health = extrude1.healthState
        if health == adsk.fusion.FeatureHealthStates.WarningFeatureHealthState or health == adsk.fusion.FeatureHealthStates.ErrorFeatureHealthState:
            message = extrude1.errorOrWarningMessage
        
        # Get the state of timeline object
        timeline = design.timeline
        timelineObj = timeline.item(timeline.count - 1)
        health = timelineObj.healthState
        message = timelineObj.errorOrWarningMessage

        # Get the extrusion body
        return extrude1.bodies.item(0)

    def createNonRationalCurvesFromPathJSData(data, rootComp):
        sketch = rootComp.sketches.add(rootComp.xZConstructionPlane)
        sketch.name = 'pathCurve'
        curves = adsk.core.ObjectCollection.create()

        for i in range(len(data)):
            knots = [0,0,0,0,1,1,1,1]

            # list of controlpoints
            curveControlPoints = getCurveControlPoints(data[i])

            # Create the spline.
            nonRational = adsk.core.NurbsCurve3D.createNonRational(curveControlPoints, 3, knots, False)

            crv = sketch.sketchCurves.sketchFittedSplines.addByNurbsCurve(nonRational)
            curves.add(crv)

        return curves

    def getCurveControlPoints(curveData):
        create = adsk.core.Point3D.create

        return [
            create3DPointFromControlPoint(curveData['controlpoints'][0]),
            create3DPointFromControlPoint(curveData['controlpoints'][1]),
            create3DPointFromControlPoint(curveData['controlpoints'][2]),
            create3DPointFromControlPoint(curveData['controlpoints'][3])
        ]

    def create3DPointFromControlPoint(controlPoint):
        return adsk.core.Point3D.create(
            controlPoint['x'],
            controlPoint['y'],
            0
        )

    DEFAULT_NAME = 'planeOnCurve'

    def createConstructionPlanesOnPathByName(constructionPlanes, curves, profiles):
        planeNames = []
        path = adsk.fusion.Path.create(curves[0], adsk.fusion.ChainedCurveOptions.connectedChainedCurves)

        for i in range(len(profiles)):
            planeInput = constructionPlanes.createInput()
            planeInput.setByDistanceOnPath(path, adsk.core.ValueInput.createByReal(profiles[i]['time']))
            plane = constructionPlanes.add(planeInput)
            plane.name = getPlaneName(planeNames)
            planeNames.append(plane.name)

        return planeNames

    def getPlaneName(planeNames):
        return "%s-%02d" % (DEFAULT_NAME, len(planeNames))

    def getConstructionPlanePositionsProfiles(profiles):
        planePositions = []

        for i in range(len(profiles)):
            planePositions.append(getPathPointDict(profiles[i]))
        
        return planePositions

    def getPathPointDict(profileData):
        dictionary = {}
        point = create3DPointFromControlPoint(profileData)
        normalVector = createNormalVectorFromCurveData(profileData)
        time = profileData['time']
        breakTime = profileData['breakTime']
        flip = profileData['flip']

        return {
            'controlpoint': point,
            'normalVector': normalVector,
            'time': time,
            'breakTime': breakTime,
            'flip': flip
        }

    def createNormalVectorFromCurveData(curveDataControlPoint):
        normalVector = adsk.core.Vector3D.create(
            curveDataControlPoint['normalX'] - curveDataControlPoint['x'],
            0,
            -1 * curveDataControlPoint['normalY'] + curveDataControlPoint['y'],
        )
        normalVector.normalize()

        return normalVector

    def mapTime(positions, leftBreak):
        for i in range(len(positions)):
            timeValue = positions[i]['time']
            breakTime = positions[i]['breakTime']
            if (timeValue <= breakTime):
                positions[i]['timeMapped'] = leftBreak * (timeValue / breakTime)
            else:
                positions[i]['timeMapped'] = leftBreak + (1 - leftBreak) * (timeValue - breakTime) / (1 - breakTime)

        return positions

    def mapScaleRight(positions, leftBreak):

        for i in range(len(positions)):
            timeValue = positions[i]['time']
            breakTime = positions[i]['breakTime']
            addEndScaleX = 0.5
            endNArrowX = 0.1
            addEndScaleY = 1
            if (timeValue <= breakTime):
                positions[i]['scaleXRight'] = 1 + mapX(timeValue) * addEndScaleX
                positions[i]['scaleYRight'] = 1 + mapX(timeValue) * addEndScaleY
                positions[i]['preBreak'] = True
            else:
                tt = (timeValue - breakTime)/(1 - breakTime)
                # narrow = mapXInv(tt)
                narrow = 1
                positions[i]['scaleXRight'] = (1 + mapX(timeValue) * addEndScaleX) * (endNArrowX + (1-endNArrowX) * narrow)
                positions[i]['scaleYRight'] = 1 + mapX(timeValue) * addEndScaleY
                positions[i]['preBreak'] = False

        return positions

    def mapScaleLeft(positions, leftBreak):

        for i in range(len(positions)):
            timeValue = positions[i]['time']
            breakTime = positions[i]['breakTime']
            addEndScaleX = 1
            addEndScaleY = 1

            positions[i]['scaleXLeft'] = 1 + mapX(timeValue) * addEndScaleX
            positions[i]['scaleYLeft'] = 1 + mapX(timeValue) * addEndScaleY
            positions[i]['preBreak'] = None

        return positions

    def mapX(time):
        return 0.5 * (1 + math.sin(math.pi * (time - 0.5)))

    def mapXInv(time):
        return 0.5 * (1 + math.sin(math.pi * ((1 - time) - 0.5)))

    SAFE = 1

    def createProfilesAlongPathShort(rootComp, planeAtPath, constructionPlanePosition, bodyName, scaleXName, scaleYName):
        time = constructionPlanePosition['timeMapped']
        flip = constructionPlanePosition['flip']
        scaleX = constructionPlanePosition[scaleXName]
        scaleY = constructionPlanePosition[scaleYName]
        normalVector = constructionPlanePosition['normalVector']
        crvPoint = constructionPlanePosition['controlpoint']
        body = rootComp.bRepBodies.itemByName(bodyName)

        if (scaleX == None or scaleY == None):
            return None

        # calc body depth
        bodyBB = body.boundingBox
        bodyDept = math.fabs(bodyBB.minPoint.z - bodyBB.maxPoint.z) * SAFE

        newBody = body.copyToComponent(rootComp)

        m1 = adsk.core.Matrix3D.create()
        m1.translation = adsk.core.Vector3D.create(0, 0, 0.5 * bodyDept)

        m2 = adsk.core.Matrix3D.create()
        m2.setToRotateTo(
            adsk.core.Vector3D.create(-1, 0, 0),
            normalVector,
            adsk.core.Vector3D.create(0, 1, 0)
        )
        m22 = adsk.core.Matrix3D.create()
        m22.setToRotation(math.pi, rootComp.yConstructionAxis.geometry.getData()[2], rootComp.originConstructionPoint.geometry)

        m3 = adsk.core.Matrix3D.create()
        m3.translation = adsk.core.Vector3D.create(crvPoint.x, 0, -crvPoint.y)

        m4 = adsk.core.Matrix3D.create()
        m4.transformBy(m1)
        m4.transformBy(m2)
        if (flip == -1):
            m4.transformBy(m22)
        m4.transformBy(m3)

        bodyColl = adsk.core.ObjectCollection.create()
        bodyColl.add(newBody)

        # scale non-uniform
        scalePointInput = rootComp.constructionPoints.itemByName('Origin')
        scaleFactor = adsk.core.ValueInput.createByReal(1)
        scaleInput = rootComp.features.scaleFeatures.createInput(bodyColl, scalePointInput, scaleFactor)
        xScale = adsk.core.ValueInput.createByReal(scaleX)
        yScale = adsk.core.ValueInput.createByReal(scaleY)
        zScale = adsk.core.ValueInput.createByReal(1)
        scaleInput.setToNonUniform(xScale, yScale, zScale)
        scale = rootComp.features.scaleFeatures.add(scaleInput)
        # end scale

        if (not m4.isEqualTo(adsk.core.Matrix3D.create())):
            moveInput = rootComp.features.moveFeatures.createInput(bodyColl, m4)
            moveFeat = rootComp.features.moveFeatures.add(moveInput)

        sketchAtPath = rootComp.sketches.add(planeAtPath)

        sketchAtPath.intersectWithSketchPlane([newBody])

        # hide component
        newBody.isLightBulbOn = False
        # sketchAtPath.isLightBulbOn = False

        return sketchAtPath.profiles.item(0)

    def createScalePoint(rootComp, design):
        # see https://forums.autodesk.com/t5/fusion-360-api-and-scripts/enviroment-not-supported-error-creating-a-construction-point/m-p/6368769
        baseFeat = None
        if design.designType == adsk.fusion.DesignTypes.ParametricDesignType:
            baseFeat = rootComp.features.baseFeatures.add()
        if baseFeat:
            baseFeat.startEdit()

        constructionPoints = rootComp.constructionPoints
        pointInput = constructionPoints.createInput()
        point = adsk.core.Point3D.create(0, 0, 0)
        pointInput.setByPoint(point)
        cp1 = constructionPoints.add(pointInput)
        cp1.name = "Origin"

        if baseFeat:
            baseFeat.finishEdit()

    def createLoftFromProfiles(rootComp, constructionPlanePositions, profileName, nonRationalCurves, inOneGo):
        if (inOneGo == True):
            createLoftFromProfilesOneGo(rootComp, constructionPlanePositions, profileName, nonRationalCurves)

        else:
            for i in range(len(constructionPlanePositions) - 1):
                if profileName in constructionPlanePositions[i] and profileName in constructionPlanePositions[i + 1]:
                    createLoftFromTwoProfiles(
                        rootComp,
                        constructionPlanePositions[i][profileName],
                        constructionPlanePositions[i + 1][profileName],
                        nonRationalCurves[0]
                    )
    def createLoftFromProfilesOneGo(rootComp, constructionPlanePositions, profileName, nonRationalCurves):
        # Create loft feature input
        loftFeats = rootComp.features.loftFeatures
        loftInput = loftFeats.createInput(adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
        loftSectionsObj = loftInput.loftSections

        for i in range(len(constructionPlanePositions)):
            if profileName in constructionPlanePositions[i]:
                loftSectionsObj.add(constructionPlanePositions[i][profileName])

        loftInput.isSolid = True

        # create path from first curve
        path = rootComp.features.createPath(nonRationalCurves[0])
        loftInput.centerLineOrRails.addCenterLine(path)

        # Create loft feature
        loftFeats.add(loftInput)

    def createLoftFromTwoProfiles(rootComp, profile0, profile1, nonRationalCurve):
        # Create loft feature input
        loftFeats = rootComp.features.loftFeatures
        loftInput = loftFeats.createInput(adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
        loftSectionsObj = loftInput.loftSections
        loftSectionsObj.add(profile0)
        loftSectionsObj.add(profile1)
        loftInput.isSolid = True

        # create path from first curve
        path = rootComp.features.createPath(nonRationalCurve)
        loftInput.centerLineOrRails.addCenterLine(path)

        # Create loft feature
        loftFeats.add(loftInput)

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

        # load points from a json string
        pointData = json.loads(points)

        doc = app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)
        design = app.activeProduct

        # Get the root component of the active design.
        rootComp = design.rootComponent

        # Get extrude features
        extrudes = rootComp.features.extrudeFeatures

        # create profile project bodies
        bodyRight = createExtrude(rootComp, design, pointData, 1)
        bodyRight.name = "shortRight"
        bodyLeft = createExtrude(rootComp, design, pointData, -1)
        bodyLeft.name = "shortLeft"
        createScalePoint(rootComp, design)

        # load points from a json string
        jsonData = json.loads(curvesProfiles)
        data = jsonData[0]['curves']
        profiles = jsonData[0]['profiles']

        # create sketch 'pathCurve' with nonRational spline(s) from PathJS-data
        nonRationalCurves = createNonRationalCurvesFromPathJSData(data, rootComp)

        # create planes along path(s) and return their names
        planeNames = createConstructionPlanesOnPathByName(rootComp.constructionPlanes, nonRationalCurves, profiles)

        # get list of points and normal vectors from PathJS-data curve segements
        constructionPlanePositions = getConstructionPlanePositionsProfiles(profiles)

        # extend constructionPlanePositions with plane names
        for i in range(len(planeNames)):
            constructionPlanePositions[i]['planeName'] = planeNames[i]

        # prepare data
        LEFT_BREAK = 83.368/100
        constructionPlanePositions = mapTime(constructionPlanePositions, LEFT_BREAK)
        constructionPlanePositions = mapScaleLeft(constructionPlanePositions, LEFT_BREAK)
        constructionPlanePositions = mapScaleRight(constructionPlanePositions, LEFT_BREAK)

        # create profiles on planes along path
        for i in range(len(constructionPlanePositions)):

            # this condition succeeds:
            # if i > 1 and i < 8:

            # this condition fails because the created loft will intersect itself:
            if i > 1 and i < 9:
                constructionPlanePositions[i]['profileRight'] = createProfilesAlongPathShort(
                    rootComp,
                    rootComp.constructionPlanes.itemByName(constructionPlanePositions[i]['planeName']),
                    constructionPlanePositions[i],
                    'shortRight',
                    'scaleXRight',
                    'scaleYRight'
                )

        createLoftFromProfiles(rootComp, constructionPlanePositions, 'profileRight', nonRationalCurves, False)

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

 

0 Likes
Message 4 of 8

kandennti
Mentor
Mentor

I was surprised at the size of the code....

 

Created a function to check the intersection of two profiles.

def isIntersect(
    pro1 :adsk.fusion.Profile,
    pro2 :adsk.fusion.Profile) -> bool:

    def isIntersectWithSurf2Edges(
        surf :adsk.fusion.BRepFace,
        edges :adsk.fusion.BRepEdges):

        eva :adsk.core.SurfaceEvaluator = surf.evaluator
        geo :adsk.core.Plane = surf.geometry
        for edge in edges:
            inters :adks.fusion.objectCollection = geo.intersectWithCurve(edge.geometry)
            if inters.count < 1:
                continue
            interLst = [i for i in inters]
            res, prms = eva.getParametersAtPoints(interLst)
            if not res:
                continue
            for prm in prms:
                if eva.isParameterOnFace(prm):
                    return True
        return False

    app  :adsk.core.Application = adsk.core.Application.get()
    des  :adsk.fusion.Design = app.activeProduct
    root :adsk.fusion.Component = des.rootComponent

    # patch
    newBody = adsk.fusion.FeatureOperations.NewBodyFeatureOperation
    patches = root.features.patchFeatures
    patchInput = patches.createInput(pro1, newBody)
    patch1 = patches.add(patchInput)
    patchInput = patches.createInput(pro2, newBody)
    patch2 = patches.add(patchInput)

    surf1 :adsk.fusion.BRepFace = patch1.bodies.item(0).faces.item(0)
    surf2 :adsk.fusion.BRepFace = patch2.bodies.item(0).faces.item(0)

    # is Intersect?
    overlapRes :bool = isIntersectWithSurf2Edges(surf1, surf2.edges)
    if not overlapRes:
        overlapRes = isIntersectWithSurf2Edges(surf2, surf1.edges)
    
    # remove patch
    patch1.deleteMe()
    patch2.deleteMe()

    return overlapRes

And added to the "createLoftFromTwoProfiles" function.

・・・
    def createLoftFromTwoProfiles(rootComp, profile0, profile1, nonRationalCurve):
        # check two profile Intersect
        if isIntersect(profile0, profile1):
            return

        # Create loft feature input
        loftFeats = rootComp.features.loftFeatures
        loftInput = loftFeats.createInput(adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
        loftSectionsObj = loftInput.loftSections
・・・

If the intersection occurs between the two profiles, we stop creating the loft.
please make sure.

 

The file is attached because the code is long.

0 Likes
Message 5 of 8

wh6Q9NU
Advocate
Advocate

Thank you!

But before I have a closer look at your solution for detecting intersecting profiles I've added another set of curves/profiles (same script) in attached zip. This will fail with the message "...RuntimeError: 5: The loft would intersect itself..." while the profiles are NOT intersecting at all!

 

...
            # this condition fails because the created loft will intersect itself (not the profiles!):
            if i > 0 and i < 6:
                constructionPlanePositions[i]['profileRight'] = createProfilesAlongPathShort(
                    rootComp,
                    rootComp.constructionPlanes.itemByName(constructionPlanePositions[i]['planeName']),
                    constructionPlanePositions[i],
                    'shortRight',
                    'scaleXRight',
                    'scaleYRight'
                )

        createLoftFromProfiles(rootComp, constructionPlanePositions, 'profileRight', nonRationalCurves, False)
...

 

0 Likes
Message 6 of 8

wh6Q9NU
Advocate
Advocate

I found one fix that I will investigate more and that is setting the "setDirectionEndCondition" for the second profile of each loft. See the red marked code below (adopted from this post: https://forums.autodesk.com/t5/fusion-360-api-and-scripts/loft-profile-setdirectionendcondition/m-p/...)

 

    def createLoftFromTwoProfiles(rootComp, profile0, profile1, nonRationalCurve):
        # Create loft feature input
        loftFeats = rootComp.features.loftFeatures
        loftInput = loftFeats.createInput(adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
        loftSectionsObj = loftInput.loftSections
        section0 = loftSectionsObj.add(profile0)
        section1 = loftSectionsObj.add(profile1)
        section1.setDirectionEndCondition(adsk.core.ValueInput.createByString('0.0 deg'), adsk.core.ValueInput.createByReal(1.0))
        loftInput.isSolid = True
0 Likes
Message 7 of 8

kandennti
Mentor
Mentor

The business is busy and I can not see much.

 

The isIntersect function I created only checks that the profiles intersect, so this one doesn't stop the error.

I can't think of a good way.

0 Likes
Message 8 of 8

kandennti
Mentor
Mentor
Accepted solution

I tried a little more.

 

We found that deleting the timeline can avoid the error, and fixed the following.

・・・
    def createLoftFromTwoProfiles(rootComp, profile0, profile1, nonRationalCurve):
        try:
            # Create loft feature input
            loftFeats = rootComp.features.loftFeatures
            loftInput = loftFeats.createInput(adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
            loftSectionsObj = loftInput.loftSections
            loftSectionsObj.add(profile0)
            loftSectionsObj.add(profile1)
            loftInput.isSolid = True

            # create path from first curve
            path = rootComp.features.createPath(nonRationalCurve)
            loftInput.centerLineOrRails.addCenterLine(path)

            # Create loft feature
            loftFeats.add(loftInput)
        except:
            # Delete timeline in case of error
            app :adsk.core.Application = adsk.core.Application.get()
            des :adsk.fusion.Design = app.activeProduct
            tl  :adsk.fusion.Timeline = des.timeline

            tl.moveToPreviousStep()
            tl.deleteAllAfterMarker()
・・・