Fusion 360 API Combine Error - "Failed to get owner occurrence transform"

Fusion 360 API Combine Error - "Failed to get owner occurrence transform"

jonrbloom
Advocate Advocate
1,170 Views
2 Replies
Message 1 of 3

Fusion 360 API Combine Error - "Failed to get owner occurrence transform"

jonrbloom
Advocate
Advocate

I am experiencing an issue with the Fusion 360 API, when using the Combine Feature to cut a body.

The operation generates incorrect results, and the timeline shows that the operation has generated an error.

Reviewing the warning tells me "Failed to get owner occurrence transform", and "Failed to get target transform".

 

However, when I simply open the feature (double-click in the timeline) and close, without changing any of the inputs, the warning goes away, and my geometry is assembled correctly.

 

My actual script is large. So I spent a couple of hours cutting it down to something more digestible. It's still a little long, but this is the best I could manage. Hopefully, it's enough to home in on the error.

 

See screencast, and attached script.

 

#Author-Jon Bloomfield
#Description-Custom Cabinet Builder

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

def run(context):
    global design

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

        design = app.activeProduct

        rootComp = design.rootComponent
        door = buildShakerDoor(rootComp, "Door")

        camera = app.activeViewport.camera
        camera.isFitView = True
        app.activeViewport.camera = camera

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
    
def buildShakerDoor(parentComp: adsk.fusion.Component,
                    name: str):
    # Create a component under parent component
    transform = adsk.core.Matrix3D.create()
    occ: adsk.fusion.Occurrence = parentComp.occurrences.addNewComponent(transform)
    comp = occ.component
    comp.name = "Door"

    # The Left Stile
    left = createNewComp(comp, "DoorStile")
    stileComp: adsk.fusion.Component = left.component

    ext1 = buildBox(stileComp, stileComp.xYConstructionPlane, 60, 2, 5)

    # Get inside face
    stileRearFace = ext1.endFaces.item(0)

    # Get top edge of the face
    stileTopEdge = getEdgesByAxis(stileRearFace,
                                stileComp.yConstructionAxis,
                                stileComp.xYConstructionPlane)[0]

    # The Upper Rail
    top = createNewComp(comp, "DoorRail")
    railComp: adsk.fusion.Component = top.component

    ext2 = buildBox(railComp, railComp.xYConstructionPlane,
                    40.5, 2, 5)
    
    # Get rear face of rail
    railRearFace = ext2.endFaces.item(0)

    # Get top edge of the rear face
    railTopEdge = getEdgesByAxis(railRearFace,
                                railComp.yConstructionAxis,
                                railComp.yZConstructionPlane)[0]

    # Join top rail to left stile
    makeJoint(comp,
            stileRearFace.createForAssemblyContext(left),
            stileTopEdge.createForAssemblyContext(left),
            adsk.fusion.JointKeyPointTypes.EndKeyPoint,

            railRearFace.createForAssemblyContext(top), 
            railTopEdge.createForAssemblyContext(top),
            adsk.fusion.JointKeyPointTypes.EndKeyPoint,

            isFlipped = False,
            jointAngle=adsk.core.ValueInput.createByReal(math.pi/2),
            jointOffsetX=adsk.core.ValueInput.createByReal(0.5))

    # Scribe the rail ends by using a Cut-Combine
    toolBodies = adsk.core.ObjectCollection.create()
    toolBodies.add(left.component.bRepBodies[0].createForAssemblyContext(left))
    combines1 = comp.features.combineFeatures
    combInput1 = combines1.createInput(targetBody=top.component.bRepBodies[0].createForAssemblyContext(top),
                                       toolBodies=toolBodies)

    combInput1.isKeepToolBodies = True
    combInput1.operation = adsk.fusion.FeatureOperations.CutFeatureOperation

    # Create the combine feature
    comb1 = combines1.add(combInput1)    

    return occ

#######################################################################################
### Utilities
#######################################################################################


def makeJoint(parentComp,
              face1,
              edge1,
              joint1Type,
              face2,
              edge2,
              joint2Type,
              jointAngle=None,
              isFlipped=False,
              jointOffsetX=None,
              jointOffsetY=None,
              jointOffsetZ=None):
    geo0 = adsk.fusion.JointGeometry.createByPlanarFace(face1, edge1, joint1Type)
    geo1 = adsk.fusion.JointGeometry.createByPlanarFace(face2, edge2, joint2Type)

    jointInput: adsk.fusion.JointInput = parentComp.joints.createInput(geo1, geo0)
    jointInput.setAsRigidJointMotion()

    if jointAngle != None:
        jointInput.angle = jointAngle

    if jointOffsetZ != None:
        jointInput.offset = jointOffsetZ

    jointInput.isFlipped = isFlipped
    joint: adsk.fusion.Joint = parentComp.joints.add(jointInput)

    if jointOffsetX or jointOffsetY:
        # Joint is missing essential properties for X/Y offset
        # Get them by identifying the zOffset (aka offset) parameter and indexing
        # back from there 
        [pre, idx] = joint.offset.name.split('d')
        nameIdx = int(idx)

        if jointOffsetX:
            offsetX = joint.parentComponent.modelParameters.itemByName('d{}'.format(nameIdx-2))  
            offsetX.expression = str(jointOffsetX.realValue) + "cm"

            # Self-test to make sure we hit the mark
            assert(offsetX.role == "alignOffsetX")

        if jointOffsetY:
            offsetY = joint.parentComponent.modelParameters.itemByName('d{}'.format(nameIdx-1))
            offsetY.expression = str(jointOffsetY.realValue) + "cm"

            # Self-test to make sure we hit the mark
            assert(offsetY.role == "alignOffsetY")

    return joint

def createNewComp(parentComp, compName) -> adsk.fusion.Occurrence:
    allOccs = parentComp.occurrences

    # Create a component under parentComp
    occ = allOccs.addNewComponent(adsk.core.Matrix3D.create())
    comp = occ.component
    comp.name = compName
    return occ

def buildBox(comp, plane, width, height, depth):
    # Create a new sketch on the plane.
    sketches = comp.sketches
    sketch = sketches.add(plane)
    sketch.name = "Profile"

    distanceInput = adsk.core.ValueInput.createByReal(depth)

    sketchLines = sketch.sketchCurves.sketchLines
    origin = adsk.core.Point3D.create(0, 0, 0)
    topRight = adsk.core.Point3D.create( width, height, 0)
    sketchLines.addTwoPointRectangle(origin, topRight)

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

    # Create an extrusion input
    extrudes1 = comp.features.extrudeFeatures
    extInput1 = extrudes1.createInput(prof, adsk.fusion.FeatureOperations.NewBodyFeatureOperation)

    # Set the distance extent
    extInput1.setDistanceExtent(False, distanceInput)

    # Set the extrude type to be solid
    extInput1.isSolid = True

    # Create the extrusion
    ext1 = adsk.fusion.ExtrudeFeature.cast(extrudes1.add(extInput1))

    return ext1


# Return the distance between an edge and plane
# edge: (BRepEdge) The line to evaluate
# plane: (Plane3D) The plane
def edgeToPlane(edge:  adsk.fusion.BRepEdge,
               plane: adsk.core.Plane):
    start: adsk.core.Point3D = edge.geometry.startPoint
    end:   adsk.core.Point3D = edge.geometry.endPoint

    orthEnd: adsk.core.Vector3D = plane.normal
    orthEnd.add(start.asVector())
    orthLine = adsk.core.Line3D.create(start, orthEnd.asPoint())
    orthogonal = orthLine.asInfiniteLine()

    isectPoint: adsk.core.Point3D = plane.intersectWithLine(orthogonal)
    distance = isectPoint.distanceTo(start)

    return distance

            
# Return all the edges from 'face' that are parallel to 'axis'
# Edges are sorted by distance from 'plane'
def getEdgesByAxis(face: adsk.fusion.BRepFace,
                   axis: adsk.fusion.ConstructionAxis,
                   plane: adsk.fusion.ConstructionPlane):
    def distanceSort(e):
        return e[1]

    constrAxis: adsk.core.InfiniteLine3D = axis.geometry
    axisDirection = constrAxis.direction

    edgeList = []    

    edges: adsk.fusion.BRepEdges = face.edges
    for e in edges:
        edge = adsk.fusion.BRepEdge.cast(e)
        edgeDirection: adsk.core.Vector3D = edge.geometry.asInfiniteLine().direction

        if edgeDirection.isParallelTo(axisDirection):
            dist = edgeToPlane(edge, plane.geometry)
            edgeList.append( (edge, dist) )

    edgeList.sort(key = distanceSort)

    finalList = []
    for i in range(len(edgeList)):
        finalList.append(edgeList[i][0])

    return finalList
0 Likes
1,171 Views
2 Replies
Replies (2)
Message 2 of 3

jonrbloom
Advocate
Advocate

This was my attempt at a screencast. Looks like I fumbled 🙄

Hopefully it will come through this time.

0 Likes
Message 3 of 3

jonrbloom
Advocate
Advocate

Third time lucky!

 

0 Likes