Announcements
Attention for Customers without Multi-Factor Authentication or Single Sign-On - OTP Verification rolls out April 2025. Read all about it here.

Make occurrences independent

j.han97
Advocate

Make occurrences independent

j.han97
Advocate
Advocate

Hi all,

 

Let me introduce my problem briefly: The models I am working with are imported from external CAD program, and they often consist of multiple occurrences of a single component.

 

However, in my working process, I do not want the items to be 'linked' (i.e., when I modify one of the occurrences, I do NOT want other occurrences to be modified as well.

 

Currently, I am using the API to search for duplicate occurrences (components which have more than one occurrences in the structure) and move the content of the duplicate occurrences to a newly created component. In short, this makes them independent of each other by creating unique components for them.

 

The problem is, while this method works as I intended, it happens to be extremely time consuming. For a model that consist of 676 occurrences, it took about 26 min 38 sec.

 

Is there any alternative method to achieve the same objective faster?

 

Thank you all in advance.

0 Likes
Reply
692 Views
7 Replies
Replies (7)

BrianEkins
Mentor
Mentor

It's difficult to say what might be faster when it's not clear what you're doing now.  Can you describe in detail what you're doing now or the best is to provide some sample code and a file to test with.

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
0 Likes

j.han97
Advocate
Advocate

Hi @BrianEkins ,

 

Thank you for your reply! I will put my code here for this function.

#globals
_app = adsk.core.Application.get()

#Main 
def run(context):
    root_comp = adsk.fusion.Design.cast(_app.activeProduct).rootComponent
    find_duplicated_occurrences(root_comp) #See below

def find_duplicated_occurrences(comp: adsk.fusion.Component):
    #This function searches for any duplicated occurrences
    #I.e., a component with multiple occurrences in the model
    occs = list(comp.occurrences)
    unique_comp = []
    for occ in occs:
        if occ.childOccurrences:
            #Search recursively through tree of occurrences
            find_duplicated_occurrences(occ.component)
        
        if occ.component not in unique_comp:
            unique_comp.append(occ.component)
        else:
            new_occ = comp.occurrences.addNewComponent(adsk.core.Matrix3D.create())
            copy_occ_to(occ, new_occ) #See below
            remove_occ(occ) #See below

def copy_occ_to(source: adsk.fusion.Occurrence, target: adsk.fusion.Occurrence):
    #This function copies content of an occurrence to another occurrence
    target.component.name = source.name.replace(':', '_') #Preserve the name
    #Copy the bodies
    for source_body in source.bRepBodies:
        source_body.copyToComponent(target)
    #Copy child occurrences
    for source_child in source.childOccurrences:
        target_child = target.component.occurrences.addNewComponent(adsk.core.Matrix3D.create())
        #Similarly, traverse recursively to move child occurrences
        copy_occ_to(source_child, target_child)

def remove_occ(occ: adsk.fusion.Occurrence):
    #This function uses removeFeatures to remove occurrence
    root_comp = adsk.fusion.Design.cast(_app.activeProduct).rootComponent
    remove_features = root_comp.features.removeFeatures
    remove_features.add(occ)

I hope for your suggestions and possible improvement for my code. Thank you!

0 Likes

BrianEkins
Mentor
Mentor

Do you also have a model we can use for testing?  That will make it easier than trying to find something or building one from scratch.

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
0 Likes

j.han97
Advocate
Advocate

Hi @BrianEkins ,

 

Here is a simple model with one component (Comp_A) with four occurrences. With the script they will turn into four components (Comp_A_1, Comp_A_2, Comp_A_3, Comp_A_4).

 

Thank you!

0 Likes

j.han97
Advocate
Advocate

In case anyone is interested in this, I have made some modifications to the function. In short, I simply switch to direct design mode temporarily because the internal functions (copyToComponent, addNewComponent, etc) have considerably greater performance in direct design mode. This reduces the execution time at the cost of losing feature information (which I do not need for this stage, so not a disadvantage at all).

 

As a comparison, execution time using previous code is 11.433 seconds while the current code takes 2.641 seconds for a model with 56 occurrences.

 

The code is posted below for those who need it:

import adsk.core, adsk.fusion, traceback
import cProfile, pstats, os

def find_duplicate_occurrences(comp: adsk.fusion.Component):
    #This function traverse recursively to search for duplicated occurrences
    #I.e., multiple occurrences which belong to a single component
    occs = list(comp.occurrences)
    comp_list = []
    for occ in occs:
        if occ.childOccurrences:
            find_duplicate_occurrences(occ.component)
    
        if occ.component not in comp_list:
            comp_list.append(occ.component)
        else:
            #This occurrence is duplicated. Make a new component for it.\
            new_occ = comp.occurrences.addNewComponent(adsk.core.Matrix3D.create())
            copy_occ_to(occ, new_occ)
            occ.deleteMe()
    

def copy_occ_to(source: adsk.fusion.Occurrence, target: adsk.fusion.Occurrence):
    #This function copies the content of an occurrence to a new occurrence
    target.component.name = give_name(source.component.name)
    
    #root_comp = adsk.fusion.Design.cast(_app.activeProduct).rootComponent
    
    #Copy bRepBodies
    for body in source.bRepBodies:
        body.copyToComponent(target)
    
    #Copy child occurrences
    for source_occ in source.childOccurrences:
        target_occ = target.component.occurrences.addNewComponent(adsk.core.Matrix3D.create())
        copy_occ_to(source_occ, target_occ) #Again, do this recursively to copy the entire depth
    
def check_length(root_comp: adsk.fusion.Component):
    all_comp = []
    for occ in root_comp.allOccurrences:
        comp = occ.component
        if comp not in all_comp:
            all_comp.append(comp)
    _ui.messageBox(f'Before: {len(root_comp.allOccurrences)} occurrences, {len(all_comp)} components')

def give_name(original_name: str):
    global _all_comp_name
    assert original_name in _all_comp_name
    n = 1
    while True:
        name = original_name+'_'+str(n)
        if name in _all_comp_name:
            n += 1
        else:
            _all_comp_name.append(name)
            return name
    
_app = adsk.core.Application.get()
_ui = _app.userInterface
_path = __file__[:__file__.rfind('/')]
_all_comp_name = []
        
def run(context):
    try: 
        global _all_comp_name

        design = _app.activeProduct
        design.designType = adsk.fusion.DesignTypes.DirectDesignType
        #design.designType = adsk.fusion.DesignTypes.ParametricDesignType

        # Get the root component of the active design.
        root_comp = design.rootComponent
        check_length(root_comp)
        
        for occ in root_comp.allOccurrences:
            if occ.component.name not in _all_comp_name:
                _all_comp_name.append(occ.component.name)
        
        profiler = cProfile.Profile()
        profiler.enable()
        
        find_duplicate_occurrences(root_comp)

        #Check
        all_comp = []
        dup_occ = []
        for occ in root_comp.allOccurrences:
            comp = occ.component
            if comp not in all_comp:
                all_comp.append(comp)
            else:
                dup_occ.append(occ)
        
        check_length(root_comp)
        
        if len(dup_occ) > 0:
            for occ in dup_occ:
                #duplicate
                if occ.assemblyContext:
                    parent = occ.assemblyContext.component
                else:
                    parent = root_comp
                
                new_occ = parent.occurrences.addNewComponent(adsk.core.Matrix3D.create())
                copy_occ_to(occ, new_occ)
                occ.deleteMe()
        
        profiler.disable()
        
        
        stats = pstats.Stats(profiler)
        stats.strip_dirs()
        stats.sort_stats('tottime')
        profile_data_path = os.path.join(_path, 'profile-data.txt')
        stats.dump_stats(profile_data_path)
        
                
        check_length(root_comp)
        
        
    except:
        if _ui:
            _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

Few points to note:

1. Some utility functions are added: check_length to check the number of occurrences & components; give_name to decide name for new component.

2. cProfile & pstats are used to monitor the code performance. They are not required for the code to work.

3. The recursive-search method can only find duplicate occurrences under same parent. If there are multiple occurrences of a component 'scattered' under different parents (occurrences), they cannot be detected. Therefore, I have added some lines after the recursive-search function to check this. This results in a two-step process which is not elegant (at all). (Maybe) I will look into this and improve it in the future.

 

0 Likes

tykapl.breuil
Advocate
Advocate

You can maybe try using the UI only copypastenew, it might be faster and it will solve your double check problem as the new child occurences in the new component are created to be independant from the 'scattered' occurences (they are still linked to each other however).

Sample that copies the source occurence in the target parent occurence (the target occurence contains source as a child occurence) :

sels = _ui.activeSelections
sels.clear()
sels.add(source)#selects the source occurence
_app.executeTextCommand('Commands.Start CopyCommand')
#automatically puts into clipboard the selected occurence
sels.clear()
sels.add(target)
_app.executeTextCommand('Commands.Start FusionPasteNewCommand')
#pastes the occurence as a new component into the target occurence
#(the new occurence is a child occurence of target)
_app.executeTextCommand('Commands.Start CommitCommand')
#Fusion automatically starts a move command after FusionPasteNewCommand, this ends it
0 Likes

j.han97
Advocate
Advocate

Hi @tykapl.breuil ,

 

This is an interesting way to do it. I feel like it will work better than my code. I will experiment with it and compare the performances.

 

Thank you for your suggestion!

0 Likes