Getting rotation and position of occurrences in an assembly

Anonymous

Getting rotation and position of occurrences in an assembly

Anonymous
Not applicable

Background: I'm working on a script that will let me export STLs for all the unique components in my Fusion 360 assembly along with a JSON file containing the position and rotation data info for all the occurrences.  I ultimately want to use this model data to recreate my project on the web using Three.js.

 

I've got the STL export part of the script sorted but the position/rotation data looks wrong for the occurrences.  I used the following code assuming the transform information would be all I needed to position an occurrence:

  for row in range(0, 4):
    m.append([0]*4)
    for column in range(0, 4):
      m[row][column] = occurrence.transform.getCell(row, column)

 

Unfortunately, I can see in the exported data that I get the identity matrix for most of my occurrences which means I'm doing something wrong.  All the occurrences in my assembly are positioned using joints so I'm thinking I may have to use the occurrence joint information instead but I'm not sure.  Any tips would be appreciated.  Thanks

0 Likes
Reply
Accepted solutions (1)
1,144 Views
7 Replies
Replies (7)

goyals
Autodesk
Autodesk

I think it depends how you are getting the occurrence. May be you can try to get the nativeObject from the occurrence which will return you the occurrence object in respect to root component and then try your code

 

occurrence = occurrence.nativeObject 
for row in range(0, 4):
    m.append([0]*4)
    for column in range(0, 4):
      m[row][column] = occurrence.transform.getCell(row, column)

 



Shyam Goyal
Sr. Software Dev. Manager
0 Likes

Anonymous
Not applicable
Accepted solution

Thanks @Anonymous,

 

I think I found the issue after tinkering with it some more.  Here's a simplified component tree of my assembly to illustrate my issue:

 

RootComponent

->LinkedComponentA

--->ComponentB

->ComponentC

 

LinkedComponentA is positioned in the assembly using a rigid joint between ComponentB and ComponentC.  My script is using the LinkedComponentA occurrence to get the transform information.  That transform is the identity matrix so I can't position it correctly in my other project.   It turns out that the transform information I want is on the occurrence for ComponentB.  ComponentB is getting positioned in the assembly due to the joint and I'm guessing LinkedComponentA actually stays in place.

 

I updated the design for LinkedComponentA to include a RigidGroup with the root and all the children and I am now picking up the transform information off LinkedComponentA as expected.

0 Likes

andreas
Explorer
Explorer

Did you get around finishing your script? I try to do something very similar (export all parts as step files and have a list of the parts with position/rotation), so I wonder if it would be possible to have a look at your script. Would be greatly appreciated.

Andreas

0 Likes

Anonymous
Not applicable

Hey @andreas ,

 

It's been awhile since I've worked on or used this script, but I was ultimately able to get my whole assembly exported with the right positioning.  It took some reworking of some of my designs so they actually exported correctly.  I doubt this will work 100% for you but maybe it can serve as a starting point.  Hope this helps:

 

 

import adsk.core, adsk.fusion, adsk.cam, traceback
import pprint
import json
import os
import glob


outputDir = 'C:\\Users\\User\\dev\\pfweb\\dist\\assets'
jsonOutputDir = 'C:\\Users\\User\\dev\\pfweb\\src\\models'
uniqueComps = dict()
references = dict()


def traverseFolders(folder):
    parentFolder = folder.parentFolder
    if parentFolder:
        return traverseFolders(parentFolder) + '>' + folder.name
    else:
        return folder.name

def fullFolderPath(occ):
    try:
        folder = occ.component.parentDesign.parentDocument.dataFile.parentFolder
        return traverseFolders(folder)
    except Exception as identifier:
        return ' FAILED TO GET FOLDER'

def exportTest(occurrence, currentLevel):
    # TODO: This is a 'quick fix' for top level components getting exported like 'Left Section'
    # Find a better way
    if currentLevel == 1:
        if occurrence.component.partNumber == "Baltic Birch":
            return True
        else:
            return False

    if occurrence.component.partNumber == "Candy Cane Shot Section v101":
        return False

    occurrences = occurrence.childOccurrences
    if not(occurrence.isLightBulbOn):
        return False
    # if I don't have any children then I am a leaf component so I should be exported
    elif not(occurrences):
        return True


    numReferencedComponents = 0
    for i in range(0, occurrences.count):
        occ = occurrences.item(i)
        # if any of these children are visible and 
        if occ.isLightBulbOn and occ.isReferencedComponent:
            return False

    return True


def isIdentityMatrix(m):
    mIdent = m.copy()
    mIdent.setToIdentity()
    return m.isEqualTo(mIdent)

# Performs a recursive traversal of an entire assembly structure.
def traverseAssembly(occurrences, currentLevel, inputString):
    toBeExported = None

    for j in range(0, occurrences.count):
        occ = occurrences.item(j)
        toBeExported = exportTest(occ, currentLevel)

        inputString += spaces(currentLevel*5)  + ' Name: ' + occ.name + ', toBeExported: ' + str(toBeExported) + '\n'

        # TODO: This is for testing purposes, need to remove
        if not(toBeExported):
            inputString = traverseAssembly(occ.childOccurrences, currentLevel+1, inputString)
        else:
            # Fusion is outputting these units as CENTIMETERS
            m = []

            t1 = adsk.core.Matrix3D.create()
            t2 = adsk.core.Matrix3D.create()
            t3 = occ.transform

            # transform in relation to its parent component
            if occ.nativeObject:
                t1 = occ.nativeObject.transform
            # transform in relation to the current document
            if occ.assemblyContext:
                t2 = occ.assemblyContext.transform

            t1.transformBy(t2)
            
            for row in range(0, 4):
                m.append([0]*4)
                for column in range(0, 4):
                    m[row][column] = t1.getCell(row, column)
            partNumber = occ.component.partNumber
            uniqueComps[partNumber]['items'].append(m)

    return inputString

def spaces(spaceCount):
    result = ''
    for i in range(0, spaceCount):
        result += ' '

    return result

def uniqueComponents(occs):
    comps = []
    for i in range(0, occs.count):
        comps.append(occs.item(i).component)
    return comps

def run(context):
    # traverse assembly
    # locate all occurrences, part numbers that are to be exported with a position
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        
        product = app.activeProduct
        design = adsk.fusion.Design.cast(product)
        if not design:
            ui.messageBox('No active Fusion design', 'No Design')
            return

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

        # Create the title for the output.
        resultString = 'Root (' + design.parentDocument.name + ')\n'

        # identify all unique components 
        occurrences = rootComp.allOccurrences
        for i in range(0, occurrences.count):
            occ = occurrences.item(i)
            pn = occ.component.partNumber
            if pn not in uniqueComps.keys():
                uniqueComps[pn] = dict()
                references[pn] = dict()
                #uniqueComps[pn]['filename'] = str(i)
                uniqueComps[pn]['items'] = []
                references[pn]['occurrence'] = occ
                references[pn]['component'] = occ.component


        # Call the recursive function to traverse the assembly and build the output string.
        resultString = traverseAssembly(rootComp.occurrences.asList, 1, resultString)

        # Display the result.
        # Write the results to the TEXT COMMANDS window.
        textPalette = ui.palettes.itemById('TextCommands')
        if not textPalette.isVisible:
            textPalette.isVisible = True
        textPalette.writeText(resultString)

        # clear out existing files
        files = glob.glob(outputDir + '//*')
        for f in files:
            os.remove(f)


        # 
        filteredComps = dict(filter(lambda elem: len(elem[1]['items']) > 0, uniqueComps.items()))
        fn = 0
        for pn in filteredComps:
            comp = filteredComps[pn]
            if len(comp['items']) > 0:
                # iterate through uniqueComps
                # remove entries with empty item

                try:
                    filename = fn
                    filteredComps[pn]['filename'] = filename
                    fn += 1
                    exportMgr = adsk.fusion.ExportManager.cast(design.exportManager)
                    stlOptions = exportMgr.createSTLExportOptions(references[pn]['component'])
                    stlOptions.meshRefinement = adsk.fusion.MeshRefinementSettings.MeshRefinementLow
                    stlOptions.filename = outputDir +  '//' + str(filename)
                    exportMgr.execute(stlOptions)
                except Exception as e:
                    print(e)
                    print('Failed part number is: ' + pn)
                finally:
                    pass


        with open(jsonOutputDir + '//models.json', 'w') as fp:
            json.dump(filteredComps, fp, sort_keys=True, indent=4)
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

# # export code
#             # Save the file as STL.
#             exportMgr = adsk.fusion.ExportManager.cast(des.exportManager)
#             stlOptions = exportMgr.createSTLExportOptions(rootComp)
#             stlOptions.meshRefinement = adsk.fusion.MeshRefinementSettings.MeshRefinementMedium
#             stlOptions.filename = filename
#             exportMgr.execute(stlOptions)

 

2 Likes

andreas
Explorer
Explorer

Awesome, thanks a lot!

0 Likes

MichaelT_123
Advisor
Advisor

Hi Mr BlahSeanBlah,

It is a nice tidy code.

I would suggest some cosmetic changes, though. 😉

Instead, traversing a design and checking for unique components

  • if pn not in uniqueComps.keys():,
  • inputString = traverseAssembly(occ.childOccurrences, currentLevel+1, inputString)

one could use a simpler and less costly

  • design.allComponents

functionality, achieving at the same time a natural sorting of the output by a unique component.

 

Regards

MichaelT

 

 

MichaelT
0 Likes