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: 

Constructing plane by 3 points works in UI but fails in API

4 REPLIES 4
SOLVED
Reply
Message 1 of 5
michael
950 Views, 4 Replies

Constructing plane by 3 points works in UI but fails in API

 

I'm creating a construction plane out of 3 points:

 

  2 x BRepVertex

  1 x SketchPoint

 

It works when I select the items in the UI manually and run the 'Construct Plane Through Three Points' command.

 

When I try via API however I get:

 

File "/Users/.../Library/Application Support/Autodesk/webdeploy/production/651ae8c28e09a0db4994e95f1d60370aecd29c5a/Autodesk Fusion 360.app/Contents/Api/Python/packages/adsk/fusion.py", line 7158, in add
return _fusion.ConstructionPlanes_add(self, *args)
RuntimeError: 2 : InternalValidationError : data_->execute(&obj, apiName) && obj

I've selected the points in the UI and then queried in the Text Commands window to make sure I'm referencing the same three objects, e.g.:

print(adsk.core.Application.get().userInterface.activeSelections.item(0).entity.objectType)
print(str(adsk.core.Application.get().userInterface.activeSelections.item(0).entity.geometry.x))
print(str(adsk.core.Application.get().userInterface.activeSelections.item(0).entity.geometry.y))
print(str(adsk.core.Application.get().userInterface.activeSelections.item(0).entity.geometry.z))

print(adsk.core.Application.get().userInterface.activeSelections.item(1).entity.objectType)
print(str(adsk.core.Application.get().userInterface.activeSelections.item(1).entity.geometry.x))
print(str(adsk.core.Application.get().userInterface.activeSelections.item(1).entity.geometry.y))
print(str(adsk.core.Application.get().userInterface.activeSelections.item(1).entity.geometry.z))

print(adsk.core.Application.get().userInterface.activeSelections.item(2).entity.objectType)
print(str(adsk.core.Application.get().userInterface.activeSelections.item(2).entity.worldGeometry.x))
print(str(adsk.core.Application.get().userInterface.activeSelections.item(2).entity.worldGeometry.y))
print(str(adsk.core.Application.get().userInterface.activeSelections.item(2).entity.worldGeometry.z))

and compared to output from print() statements in my API script:

        print(v1.objectType)
        print(str(v1.geometry.x))
        print(str(v1.geometry.y))
        print(str(v1.geometry.z))
        
        print(v2.objectType)
        print(str(v2.geometry.x))
        print(str(v2.geometry.y))
        print(str(v2.geometry.z))
        
        print(sp1.objectType)
        print(str(sp1.worldGeometry.x))
        print(str(sp1.worldGeometry.y))
        print(str(sp1.worldGeometry.z))

I get the same results from both:

adsk::fusion::BRepVertex
4.850545360195631
0.889757470498661
95.39599727785486
adsk::fusion::BRepVertex
6.625354531748975
0.8507876550543854
95.43260985539378
adsk::fusion::SketchPoint
5.7188099999999995
0.0
94.48800000000001

But executing the 'create plane' command succeeds via UI but fails via API at the 'add()' statement.

 

plane_input = root.constructionPlanes.createInput()       
plane_input.setByThreePoints(v1, v2, sp1)        
plane = root.constructionPlanes.add(plane_input)

Is there anything I can do to find out what the API call is objecting to? Is it possible that the API is being stricter than the UI and expects all 3 points to be of the same class?

 

Thanks,

 

Michael

 

 

4 REPLIES 4
Message 2 of 5
ekinsb
in reply to: michael

I'm not able to reproduce the problem.  Here's a very simple script I wrote to test it and it works when I select two vertices and on sketch point.

 

import adsk.core, adsk.fusion, traceback

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

        # Get the active design.        
        product = app.activeProduct
        design = adsk.fusion.Design.cast(product)
        root = design.rootComponent
        
        # Get the selections.
        pnt1 = ui.activeSelections.item(0).entity
        pnt2 = ui.activeSelections.item(1).entity
        pnt3 = ui.activeSelections.item(2).entity
        
        ui.messageBox('Points: ' + pnt1.objectType + ', ' 
                      + pnt2.objectType + ', ' + pnt3.objectType)
        
        planeInput = root.constructionPlanes.createInput()                      
        planeInput.setByThreePoints(pnt1, pnt2, pnt3)
        plane = root.constructionPlanes.add(planeInput)
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

Brian Ekins
Inventor and Fusion 360 API Expert
Mod the Machine blog
Message 3 of 5
michael
in reply to: ekinsb

Ah, apologies for being unclear.

 

My script is creating this plane in the middle of a long series of steps, there is nothing selected in the UI when trying to create the plane, the script itself is referring to the points using API calls to the objects in the document rather than querying UI.activeSelections.

 

Here is a script to replicate the issue:

 

import adsk.core, adsk.fusion, adsk.cam, traceback

def create_sketch_line(sketch, point1, point2, constrainedLength=None, additionalConstraint=None):
    line = sketch.sketchCurves.sketchLines.addByTwoPoints(point1, point2)
    line.isConstruction = True
    
    if constrainedLength:
        dimensionLabelPoint = adsk.core.Point3D.create(
            (point1.geometry.x + point2.geometry.x)/2.0,
            (point1.geometry.y + point2.geometry.y)/2.0,
            (point1.geometry.z + point2.geometry.z)/2.0)
        
        dimension = sketch.sketchDimensions.addDistanceDimension(
            point1, 
            point2,
            adsk.fusion.DimensionOrientations.AlignedDimensionOrientation,
            dimensionLabelPoint)
            
        dimension.parameter.value = constrainedLength
    
    if additionalConstraint == 'h':
        sketch.geometricConstraints.addHorizontal(line)
    elif additionalConstraint == 'v':
        sketch.geometricConstraints.addVertical(line)
        
    return line


def create_round_sweep_from_line(root, sketch_line, diameter, name_suffix):
    path = root.features.createPath(sketch_line, False)
    planes = root.constructionPlanes
    planeInput = planes.createInput()
    planeInput.setByDistanceOnPath(path, adsk.core.ValueInput.createByReal(0))
    plane = planes.add(planeInput)
    plane.name = 'sweep ' + name_suffix
    
    sketches = root.sketches
    sweepSketch = sketches.add(plane)
    sweepSketch.name = 'sweep ' + name_suffix

    center = plane.geometry.origin
    center = sweepSketch.modelToSketchSpace(center)
    sweepSketch.sketchCurves.sketchCircles.addByCenterRadius(center, diameter/2.0)
    profile = sweepSketch.profiles[0]
    
    # create sweep
    sweepFeatures = root.features.sweepFeatures
    sweepInput = sweepFeatures.createInput(profile, path, adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
    sweepInput.orientation = adsk.fusion.SweepOrientationTypes.PerpendicularOrientationType
    
    sweep = sweepFeatures.add(sweepInput)    
    return sweep    


def hollow_out_sweep(root, sweep, wall):
    # create shell
    startFaces = sweep.startFaces
    endFaces = sweep.endFaces
    
    objCollection = adsk.core.ObjectCollection.create()
    for startFace in startFaces:
        objCollection.add(startFace)
    for endFace in endFaces:
        objCollection.add(endFace)

    shellFeatures = root.features.shellFeatures
    shellInput = shellFeatures.createInput(objCollection, False)
    shellInput.insideThickness = adsk.core.ValueInput.createByReal(wall)
    shellFeatures.add(shellInput)    


def create_tube_component(root, sketch_line, diameter, wall, name_suffix):
    sweep = create_round_sweep_from_line(root, sketch_line, diameter, name_suffix)
    body = sweep.bodies.item(0)
    body.name = 'Tube ' + name_suffix
    hollow_out_sweep(root, sweep, wall)
    component = body.createComponent().parentComponent
    return component


def cut(root, targetComp, toolComp):
    toolBodies = adsk.core.ObjectCollection.create()
    toolBodies.add(toolComp.bRepBodies.item(0))
    combineFeatureInput = root.features.combineFeatures.createInput(targetComp.bRepBodies.item(0), toolBodies)               
    combineFeatureInput.isKeepToolBodies = True
    combineFeatureInput.operation = adsk.fusion.FeatureOperations.CutFeatureOperation
    root.features.combineFeatures.add(combineFeatureInput)
                        
def max_body_volume(occurrence):  
    v = 0
    for b in occurrence.bRepBodies:
        if b.volume > v:
            v = b.volume
    return v            

# keeps only the largest body (by volume)             
def remove_smaller_bodies(root, component):
    occurrence = root.allOccurrencesByComponent(component).item(0)
    max_vol = max_body_volume(occurrence)
    for b in occurrence.bRepBodies:
        if b.volume < max_vol:
            root.features.removeFeatures.add(b)

def intersect(root, targetComp, toolComp, suffix):
    toolBodies = adsk.core.ObjectCollection.create()
    toolBodies.add(toolComp.bRepBodies.item(0))
    
    combineFeatureInput = root.features.combineFeatures.createInput(targetComp.bRepBodies.item(0), toolBodies)               
    combineFeatureInput.isKeepToolBodies = True
    combineFeatureInput.isNewComponent = True
    combineFeatureInput.operation = adsk.fusion.FeatureOperations.IntersectFeatureOperation
    newCombineFeature = root.features.combineFeatures.add(combineFeatureInput)
    newBody = newCombineFeature.bodies.item(0)
    newBody.name = 'Intersect ' + suffix
    newComponent = newBody.parentComponent
    newComponent.name = 'Intersect ' + suffix    
    return newComponent

def body_longest_edge(body):
    result = None
    max_length = 0.0
    for edge in body.edges:
        if edge.length > max_length:
            result = edge
            max_length = edge.length
    return result

   
def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        # start new doc
        app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType)
        design = adsk.fusion.Design.cast(app.activeProduct)
        root = design.rootComponent
        
        sketch1 = root.sketches.add(root.yZConstructionPlane)
        
        # ---------------------
        # draw 2-D side profile
        # ---------------------
        
        # define points
        pointX = sketch1.sketchPoints.add(adsk.core.Point3D.create(0,        0,      0))
        pointA = sketch1.sketchPoints.add(adsk.core.Point3D.create(0,        1,      0))
        pointB = sketch1.sketchPoints.add(adsk.core.Point3D.create(-37.2,    0,      0))
        pointC = sketch1.sketchPoints.add(adsk.core.Point3D.create(0,        13.68,  0))
        pointD = sketch1.sketchPoints.add(adsk.core.Point3D.create(-37.849,  34.774, 0))
        pointE = sketch1.sketchPoints.add(adsk.core.Point3D.create(-70.668,  -1.463, 0))

        # fix point X to sketch origin        
        sketch1.geometricConstraints.addCoincident(pointX, sketch1.originPoint)
        
        # draw lines between points
        create_sketch_line(sketch1, pointA, pointX, 1.0,   'v')
        create_sketch_line(sketch1, pointB, pointX, 37.20, 'h')
        create_sketch_line(sketch1, pointA, pointC, 12.68, 'v')
        
        create_sketch_line(sketch1, pointB, pointD, 34.78)
        create_sketch_line(sketch1, pointB, pointE, 33.50)
        create_sketch_line(sketch1, pointC, pointD, 43.33)
        create_sketch_line(sketch1, pointD, pointE, 48.89)
               
        # ---------------------
        # draw 3-D construction lines
        # ---------------------  
        
        # define Z-elevated points in sketch that will bound 3-D lines of elements
        elevPointB1 = sketch1.sketchPoints.add(adsk.core.Point3D.create(pointB.geometry.x, pointB.geometry.y, 2.2515))
        elevPointB2 = sketch1.sketchPoints.add(adsk.core.Point3D.create(pointB.geometry.x, pointB.geometry.y, 5.503))
        elevPointD = sketch1.sketchPoints.add(adsk.core.Point3D.create(pointD.geometry.x, pointD.geometry.y, 3.7525))
        elevPointE = sketch1.sketchPoints.add(adsk.core.Point3D.create(pointE.geometry.x, pointE.geometry.y, 2.2515)) 
        
        # draw lines between points
        line3dBeB2 = create_sketch_line(sketch1, pointB, elevPointB2)
        line3deB1eD = create_sketch_line(sketch1, elevPointB1, elevPointD)
        line3deB1eE = create_sketch_line(sketch1, elevPointB1, elevPointE)
        
        # create tubular components
        compSleeveB = create_tube_component(root, line3dBeB2,  1.002, 0.125, ' sleeveB')
        compTubeBD = create_tube_component(root, line3deB1eD, 1.002, 0.125, ' BD')
        compTubeBE = create_tube_component(root, line3deB1eE, 1.002, 0.125, ' BE')
        
        # use sleeve B to cut tubes
        cut(root, compTubeBD, compSleeveB)
        cut(root, compTubeBE, compSleeveB)
        
        # remove the small pieces left after cut
        remove_smaller_bodies(root, compTubeBD)    
        remove_smaller_bodies(root, compTubeBE)    
        
        # create new component from intersection
        intersectComponent = intersect(root, compTubeBD, compTubeBE, 'BD_BE')
        
        # make visible only the relevant items
        compTubeBD.isBodiesFolderLightBulbOn = False
        compTubeBE.isBodiesFolderLightBulbOn = False
        compSleeveB.isBodiesFolderLightBulbOn = False
        
        sketch1.isVisible = True
        
        # create construction plane using 3 points
        
        # find the longest edge on intersectComponent and grab the vertices
        edge = body_longest_edge(intersectComponent.bRepBodies.item(0))
        p1 = edge.startVertex
        p2 = edge.endVertex
        p3 = elevPointB1    # third point is a sketch point
        
        planeInput = root.constructionPlanes.createInput()
        
        result = planeInput.setByThreePoints(p1, p2, p3)
        
        print(result)        
        
        # Print out info about the three points before we try to create the ConstructionPlane
        print(' ')
        print(' ')
        print('+++++++++++++++++++++++++++++++++')
        print('p1: (' + str(p1.geometry.x) + ',' + str(p1.geometry.y) + ',' + str(p1.geometry.z) + ') ' + str(p1))
        print('p2: (' + str(p2.geometry.x) + ',' + str(p2.geometry.y) + ',' + str(p2.geometry.z) + ') ' + str(p2))
        print('p3: (' + str(p3.worldGeometry.x) + ',' + str(p3.worldGeometry.y) + ',' + str(p3.worldGeometry.z) + ') ' + str(p3))
        print('+++++++++++++++++++++++++++++++++')
        print(' ')
        print(' ')
        
        # The next line fails with: 
        #
        #  File ".../Autodesk Fusion 360.app/Contents/Api/Python/packages/adsk/fusion.py", line 7158, in add
        #     return _fusion.ConstructionPlanes_add(self, *args)
        #  RuntimeError: 2 : InternalValidationError : data_->execute(&obj, apiName) && obj
        #
        root.constructionPlanes.add(planeInput)
        
        # If you go into the UI and select "Intersect BD_BE:1" in browser, 
        # then use "Find in Window", you can select these three points manually,
        # then execute the following in the Text Commands pane:
        """
        app = adsk.core.Application.get()
        ui  = app.userInterface
        product = app.activeProduct
        design = adsk.fusion.Design.cast(product)
        root = design.rootComponent
        pnt1 = ui.activeSelections.item(0).entity
        pnt2 = ui.activeSelections.item(1).entity
        pnt3 = ui.activeSelections.item(2).entity
        print('p1: (' + str(pnt1.geometry.x) + ',' + str(pnt1.geometry.y) + str(pnt1.geometry.z) + ') ' + str(pnt1))
        print('p2: (' + str(pnt2.geometry.x) + ',' + str(pnt2.geometry.y) + str(pnt2.geometry.z) + ') ' + str(pnt2))
        print('p3: (' + str(pnt3.worldGeometry.x) + ',' + str(pnt3.worldGeometry.y) + str(pnt3.worldGeometry.z) + ') ' + str(pnt3))
        
        planeInput = root.constructionPlanes.createInput()
        planeInput.setByThreePoints(pnt1, pnt2, pnt3)
        plane = root.constructionPlanes.add(planeInput)
        """

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

 

Script fails at root.constructionPlanes.add(planeInput):

 

  File ".../Autodesk Fusion 360.app/Contents/Api/Python/packages/adsk/fusion.py", line 7158, in add
    return _fusion.ConstructionPlanes_add(self, *args)
RuntimeError: 2 : InternalValidationError : data_->execute(&obj, apiName) && obj

Debug print output from above in Console:

 

 

+++++++++++++++++++++++++++++++++
p1: (1.909322032241705,0.35064931227585233,37.55783524113825) <adsk.fusion.BRepVertex; proxy of <Swig Object of type 'adsk::core::Ptr< adsk::fusion::BRepVertex > *' at 0x13d844a80> >
p2: (2.6087638471288064,0.3352915843712802,37.572264091002346) <adsk.fusion.BRepVertex; proxy of <Swig Object of type 'adsk::core::Ptr< adsk::fusion::BRepVertex > *' at 0x13d8c8810> >
p3: (2.2515,0.0,37.2) <adsk.fusion.SketchPoint; proxy of <Swig Object of type 'adsk::core::Ptr< adsk::fusion::SketchPoint > *' at 0x13d844ba0> >
+++++++++++++++++++++++++++++++++

 

 

After script terminates with error, find "Intersect BD_BE:1" in window, select the three points manually in UI:

 

Screen Shot 2017-02-22 at 12.14.40 PM.png

 

Run the following in Text Commands pane to check that all info is the same:

 

        app = adsk.core.Application.get()
        ui  = app.userInterface
        product = app.activeProduct
        design = adsk.fusion.Design.cast(product)
        root = design.rootComponent
        pnt1 = ui.activeSelections.item(0).entity
        pnt2 = ui.activeSelections.item(1).entity
        pnt3 = ui.activeSelections.item(2).entity
        print('p1: (' + str(pnt1.geometry.x) + ',' + str(pnt1.geometry.y) + str(pnt1.geometry.z) + ') ' + str(pnt1))
        print('p2: (' + str(pnt2.geometry.x) + ',' + str(pnt2.geometry.y) + str(pnt2.geometry.z) + ') ' + str(pnt2))
        print('p3: (' + str(pnt3.worldGeometry.x) + ',' + str(pnt3.worldGeometry.y) + str(pnt3.worldGeometry.z) + ') ' + str(pnt3))
        

Output from above:

print('p1: (' + str(pnt1.geometry.x) + ',' + str(pnt1.geometry.y) + str(pnt1.geometry.z) + ') ' + str(pnt1))
p1: (1.909322032241705,0.3506493122758523337.55783524113825) <adsk.fusion.BRepVertex; proxy of <Swig Object of type 'adsk::core::Ptr< adsk::fusion::BRepVertex > *' at 0x13d8449c0> >


print('p2: (' + str(pnt2.geometry.x) + ',' + str(pnt2.geometry.y) + str(pnt2.geometry.z) + ') ' + str(pnt2))
p2: (2.6087638471288064,0.335291584371280237.572264091002346) <adsk.fusion.BRepVertex; proxy of <Swig Object of type 'adsk::core::Ptr< adsk::fusion::BRepVertex > *' at 0x13d844f90> >


print('p3: (' + str(pnt3.worldGeometry.x) + ',' + str(pnt3.worldGeometry.y) + str(pnt3.worldGeometry.z) + ') ' + str(pnt3))
p3: (2.2515,0.037.2) <adsk.fusion.SketchPoint; proxy of <Swig Object of type 'adsk::core::Ptr< adsk::fusion::SketchPoint > *' at 0x13d844a80> >

Coordinates and object types are the same between API and manual UI selection. Memory addresses are different, and API memory address for point 1 == UI memory address for point 3 (?), but I don't know if things got shuffled around in memory between script execution and manual UI manipulation.

 

Finally, create construction plane in Text Commands pane:

        planeInput = root.constructionPlanes.createInput()
        planeInput.setByThreePoints(pnt1, pnt2, pnt3)
        plane = root.constructionPlanes.add(planeInput)

and it gets created successfully, even though it failed when trying to do this via script.

 

Message 4 of 5
ekinsb
in reply to: michael

That's some pretty impressive code.

 

The problem is about context. The vertices you're using as input to create the construction plane are in the context of a component and not in the context of the root component.  Below is a substitute for the section of code where the construction plane is created that works.  It can be cleaned up a bit by keeping track of the occurrence as it's created instead of getting it again later like I'm doing. You can also read more about components, occurrences, and proxies.

 

        # create construction plane using 3 points
        
        # find the longest edge on intersectComponent and grab the vertices
        edge = adsk.fusion.BRepEdge.cast(body_longest_edge(intersectComponent.bRepBodies.item(0)))
        occs = root.allOccurrencesByComponent(intersectComponent)
        if occs.count == 1:
            occ = occs.item(0)
            
        edge = edge.createForAssemblyContext(occ)
        p1 = edge.startVertex
        p2 = edge.endVertex
        p3 = elevPointB1    # third point is a sketch point
        
        planeInput = root.constructionPlanes.createInput()
        
        result = planeInput.setByThreePoints(p1, p2, p3)

Brian Ekins
Inventor and Fusion 360 API Expert
Mod the Machine blog
Message 5 of 5
michael
in reply to: ekinsb

Great, thank you!

 

I'll go back and reread the API user's manual, I should absorb more now that I've gained some experience working with the API 🙂

 

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