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.