This is my first time trying to write a program for fusion, so ChatGPT carried me through this bit, but I think I'm starting to grasp the concepts. Here is my code. I have three files: an Initialize file that has almost nothing in it, it just tells it to run, and then an entry file and an operation file. It's all in Python.
Entry file:
import adsk.core
import adsk.fusion
import os
from ...lib import fusionAddInUtils as futil
from ... import config
from . import operation
app = adsk.core.Application.get()
ui = app.userInterface
# Unique command ID and metadata
CMD_ID = 'unlinkedCopy'
CMD_NAME = 'Unlinked Copy'
CMD_DESCRIPTION = 'Creates unlinked copies of the selected components'
IS_PROMOTED = True
# Panel was created in commands/__init__.py
WORKSPACE_ID = 'FusionSolidEnvironment'
PANEL_ID = 'ExpandedAssemblyPanel'
COMMAND_BESIDE_ID = 'ScriptsManagerCommand'
ICON_FOLDER = os.path.join(os.path.dirname(__file__), 'resources', '')
# Local list to prevent garbage collection
local_handlers = []
def start():
try:
cmd_def = ui.commandDefinitions.itemById(CMD_ID)
if cmd_def:
cmd_def.deleteMe()
cmd_def = ui.commandDefinitions.addButtonDefinition(
CMD_ID, CMD_NAME, CMD_DESCRIPTION, ICON_FOLDER
)
futil.add_handler(cmd_def.commandCreated, command_created)
panel = ui.allToolbarPanels.itemById(PANEL_ID)
if panel and not panel.controls.itemById(CMD_ID😞
control = panel.controls.addCommand(cmd_def, COMMAND_BESIDE_ID, False)
control.isPromoted = IS_PROMOTED
except Exception as e:
futil.handle_error(f'start: {str(e)}')
def stop():
try:
panel = ui.allToolbarPanels.itemById(PANEL_ID)
if panel:
control = panel.controls.itemById(CMD_ID)
if control:
control.deleteMe()
cmd_def = ui.commandDefinitions.itemById(CMD_ID)
if cmd_def:
cmd_def.deleteMe()
except Exception as e:
futil.handle_error(f'stop: {str(e)}')
def command_created(args: adsk.core.CommandCreatedEventArgs😞
futil.log(f'{CMD_NAME} Command Created')
command = args.command
inputs = command.commandInputs
# Selection input (allow multiple components to be selected)
select_input = inputs.addSelectionInput(
'target_components',
'Select Components',
'Choose the components to copy'
)
select_input.addSelectionFilter('Occurrences')
select_input.setSelectionLimits(1, 0) # Allow multiple selections
futil.add_handler(command.execute, command_execute, local_handlers=local_handlers)
futil.add_handler(command.destroy, command_destroy, local_handlers=local_handlers)
def command_execute(args: adsk.core.CommandEventArgs😞
# Get the selected occurrences from the command inputs
command = args.command
inputs = command.commandInputs
select_input = inputs.itemById('target_components')
if not select_input or select_input.selectionCount == 0:
ui.messageBox('No components selected to copy.')
return
# Extract occurrences
occurrences = [adsk.fusion.Occurrence.cast(select_input.selection(i).entity) for i in range(select_input.selectionCount)]
# Now call the operation with both arguments
operation.run_operation(args, occurrences)
def command_destroy(args: adsk.core.CommandEventArgs😞
futil.log(f'{CMD_NAME} Command Destroyed')
global local_handlers
local_handlers = []
Operation file:
import adsk.core
import adsk.fusion
import os
import tempfile
def sanitize_filename(name: str) -> str:
"""Sanitize the component name to remove invalid characters."""
invalid_chars = '<>:"/\\|?*'
return ''.join(c for c in name if c not in invalid_chars)
def run_operation(args, occurrences😞
app = adsk.core.Application.get()
ui = app.userInterface
design = adsk.fusion.Design.cast(app.activeProduct)
root_comp = design.rootComponent
export_mgr = design.exportManager
import_mgr = app.importManager
temp_dir = tempfile.gettempdir()
for occ in occurrences:
try:
# Generate a safe filename
safe_name = sanitize_filename(occ.component.name + " UC")
temp_path = os.path.join(temp_dir, f'{safe_name}_temp.f3d')
# Export the component to an .f3d file
export_options = export_mgr.createFusionArchiveExportOptions(temp_path, occ.component)
export_options.isComponentNative = True
if not export_mgr.execute(export_options😞
ui.messageBox(f'❌ Failed to export component: {occ.component.name}')
continue
if not os.path.exists(temp_path) or os.path.getsize(temp_path) < 1000:
ui.messageBox(f"⚠ Exported file is missing or invalid for {occ.component.name}: {temp_path}")
continue
before_count = root_comp.occurrences.count
# Import the component back into the root
import_options = import_mgr.createFusionArchiveImportOptions(temp_path)
result = import_mgr.importToTarget(import_options, root_comp)
if not result:
ui.messageBox(f"❌ Import returned False for {occ.component.name}")
continue
# Get the new occurrence
after_count = root_comp.occurrences.count
if after_count <= before_count:
ui.messageBox(f'⚠ Import succeeded but no new component found for {occ.component.name}')
continue
new_occ = root_comp.occurrences.item(after_count - 1)
if new_occ.isReferencedComponent:
new_occ.breakLink()
new_occ.component.name = safe_name
# Cleanup temp file
if os.path.exists(temp_path😞
os.remove(temp_path)
except Exception as e:
ui.messageBox(f"❌ Error during processing {occ.component.name}:\n{e}")