Demonstrates how to automate the creation of an additive SLA manufacturing setup.
To run the sample script, have a design with one or more components open in Fusion’s DESIGN workspace. This script will switch the UI from the DESIGN workspace to the MANUFACTURE workspace, create a new Manufacturing Model, and create an Additive setup using that manufacturing model as an input.
The setup will select a SLA 3D printer from Fusion’s machine library and a print setting from the print setting library. All components in the Manufacturing model will be automatically oriented and arranged within the build area of the selected SLA machine. This script will also create support structures, based on the orientation of each component.
The support and orientation operations are created from a template. The script further demonstrates how to wrap script code into a command such that only one undo entry is created for the entire script instead of one entry per internal action.
import adsk.core, adsk.fusion, adsk.cam, traceback, tempfile, time app = adsk.core.Application.get() ui = app.userInterface # Global set of event handlers to keep them referenced for the duration of the command handlers = [] def getTemplateFromLibrary(templateLibrary: adsk.cam.CAMTemplateLibrary, libLocation: adsk.cam.LibraryLocations, folderName: str, templateNameSubstring: str): libraryURL = templateLibrary.urlByLocation(libLocation) templates = None if folderName == "": templates = templateLibrary.childTemplates(libraryURL) else: childFolders = templateLibrary.childFolderURLs(libraryURL) pickedFolderURL = None for folderURL in childFolders: if folderURL.leafName.lower() == folderName.lower(): pickedFolderURL = folderURL break templates = templateLibrary.childTemplates(pickedFolderURL) pickedTemplate = None for template in templates: lowerCaseName: str = template.name.lower() if lowerCaseName.find(templateNameSubstring.lower()) != -1: pickedTemplate = template break return pickedTemplate def createManufacturingModel(cam: adsk.cam.CAM): showMessage('=============================================') showMessage('Creating Manufacturing Model...') manufacturingModels = cam.manufacturingModels mmInput = manufacturingModels.createInput() mmInput.name = "My Manufacturing Model - SLA" manufacturingModel = manufacturingModels.add(mmInput) showMessage('Getting occurrences...') occs = getValidOccurrences(manufacturingModel.occurrence) if len(occs) == 0: ui.messageBox('No component has been added to the scene.') return (None, []) return (manufacturingModel, occs) # Create an additive setup. def createAdditiveSetup(models: list[adsk.fusion.Occurrence], cam: adsk.cam.CAM, libraryManager: adsk.cam.CAMLibraryManager): showMessage('Creating Setup...') setups = cam.setups input = setups.createInput(adsk.cam.OperationTypes.AdditiveOperation) input.models = models input.name = 'AdditiveSetup' printSettingLibrary = libraryManager.printSettingLibrary machineLibrary = libraryManager.machineLibrary printSetting = None machine = None if True: # URL-structure browsing printSettingUrl = printSettingLibrary.urlByLocation(adsk.cam.LibraryLocations.Fusion360LibraryLocation) ## .Fusion360LibraryLocation vs .LocalLibraryLocation etc. printSettings = printSettingLibrary.childPrintSettings(printSettingUrl) machineUrl = machineLibrary.urlByLocation(adsk.cam.LibraryLocations.Fusion360LibraryLocation) ## .Fusion360LibraryLocation vs .LocalLibraryLocation etc. machines = machineLibrary.childMachines(machineUrl) for ps in printSettings: if ps.name == "Prusa SL1S SPEED - Prusament Resin Tough - 0.05mm Normal": #print setting name from fusions library printSetting = ps break for machine in machines: #model name from fusions library -- Example: "Generic SLA Machine" if machine.model == "SL1S SPEED": machine = machine break input.machine = machine input.printSetting= printSetting setup = setups.add(input) return setup def createOrientations(templateLibrary: adsk.cam.CAMTemplateLibrary, occs: list[adsk.fusion.Occurrence], setup: adsk.cam.Setup, cam: adsk.cam.CAM): # the second parameter can be any of the following: # - adsk.cam.LibraryLocations.Fusion360LibraryLocation # - adsk.cam.LibraryLocations.LocalLibraryLocation # - adsk.cam.LibraryLocations.CloudLibraryLocation # the third parameter is the folder name in the library # using the empty string for the root folder will search the templates in the top level folder instead # the fourth parameter is the substring to search for in the template name orientationTemplate = getTemplateFromLibrary(templateLibrary, adsk.cam.LibraryLocations.Fusion360LibraryLocation, "orientations", "SLA - Automatic Orientation") if orientationTemplate is None: showMessage('No orientation template found.') return orientationTemplateInput: adsk.cam.CreateFromCAMTemplateInput = adsk.cam.CreateFromCAMTemplateInput.create() orientationTemplateInput.camTemplate = orientationTemplate # Create the automatic orientation operations for each occurrence. for occ in occs: showMessage(f'Generating Automatic Orientation for "{occ.name}" ...') orientations = setup.createFromCAMTemplate2(orientationTemplateInput) orientation = orientations[0] orientation.name = 'Automatic Orientation: ' + occ.name orientationTarget = orientation.parameters.itemByName('optimizeOrientationTarget') orientationTarget.value.value = [occ] future = cam.generateToolpath(orientation) while (future.isGenerationCompleted == False): time.sleep(0.5) generatedResults = orientation.generatedDataCollection castPref = None firstResult = None primary = generatedResults.itemByIdentifier(adsk.cam.GeneratedDataType.OptimizedOrientationGeneratedDataType) if isinstance(primary, adsk.cam.OptimizedOrientationResults): castPref: adsk.cam.OptimizedOrientationResults = primary firstResult = castPref.item(0) castPref.currentOrientationResult = firstResult def createAdditiveArrange(setup: adsk.cam.Setup, cam: adsk.cam.CAM): # Define and create the arrange operation. showMessage('Generating Additive Arrange...') operationInput = setup.operations.createInput('additive_arrange') arrange = setup.operations.add(operationInput) parameter: adsk.cam.StringParameterValue = arrange.parameters.itemByName("arrange_arrangement_type").value parameter.value = 'Pack2D' # Specify the values to control the arrangement. All length units are centimeters. parameter: adsk.cam.FloatParameterValue = arrange.parameters.itemByName("arrange_platform_clearance").value parameter.value = 0.7 parameter: adsk.cam.FloatParameterValue = arrange.parameters.itemByName("arrange_frame_width").value parameter.value = 0.5 parameter: adsk.cam.FloatParameterValue = arrange.parameters.itemByName("arrange_ceiling_clearance").value parameter.value = 0.5 parameter: adsk.cam.FloatParameterValue = arrange.parameters.itemByName("arrange_object_spacing").value parameter.value = 1 future = cam.generateToolpath(arrange) while (future.isGenerationCompleted == False): time.sleep(0.5) def createSupports(templateLibrary: adsk.cam.CAMTemplateLibrary, occs: list[adsk.fusion.Occurrence], setup: adsk.cam.Setup, cam: adsk.cam.CAM): showMessage('Generating Supports...') # the second parameter can be any of the following: # - adsk.cam.LibraryLocations.Fusion360LibraryLocation # - adsk.cam.LibraryLocations.LocalLibraryLocation # - adsk.cam.LibraryLocations.CloudLibraryLocation # the third parameter is the folder name in the library # using the empty string for the root folder will search the templates in the top level folder instead # the fourth parameter is the substring to search for in the template name pickedTemplate = getTemplateFromLibrary(templateLibrary, adsk.cam.LibraryLocations.Fusion360LibraryLocation, "supports", "SLA - Braced Bar Support") if pickedTemplate is None: showMessage('No support template found.') return supportTemplateInput = adsk.cam.CreateFromCAMTemplateInput.create() supportTemplateInput.camTemplate = pickedTemplate supports = setup.createFromCAMTemplate2(supportTemplateInput) if len(supports) == 0: showMessage('No supports created.') return for support in supports: parameterName = 'supportTarget' if (support.parameters.itemByName('supportTarget') == None): parameterName = 'supportTargetModel' supportParam = support.parameters.itemByName(parameterName) supportParam.value.value = occs support.isLightBulbOn = True future = cam.generateToolpath(supports[0]) while (future.isGenerationCompleted == False): time.sleep(0.5) for support in supports: if support.hasError: showMessage(f'Support generation failed for object {support.name}: {support.errorMessage}') support.deleteMe() for i in range(setup.children.count): op = setup.children.item(i) if (op and op.strategy == "additive_support_folder"): op.isLightBulbOn = True break # Event handler that reacts to when the command is executed. class MyCommandExecuteHandler(adsk.core.CommandEventHandler): def __init__(self): super().__init__() def notify(self, args): try: # Make sure the TEXT COMMAND palette is visible. textPalette = ui.palettes.itemById('TextCommands') if not textPalette.isVisible: textPalette.isVisible = True adsk.doEvents() doc = app.activeDocument products = doc.products # Make camWS = ui.workspaces.itemById('CAMEnvironment') camWS.activate() cam = adsk.cam.CAM.cast(products.itemByProductType("CAMProductType")) # Design creation designWS = ui.workspaces.itemById('FusionSolidEnvironment') designWS.activate() design = adsk.fusion.Design.cast(products.itemByProductType("DesignProductType")) camWS.activate() libraryManager = adsk.cam.CAMManager.get().libraryManager templateLibrary = libraryManager.templateLibrary # The block below is the gist of this script. # Each method can be edited to fit the user's needs. (manufacturingModel, occs) = createManufacturingModel(cam) if manufacturingModel is None: return setup: adsk.cam.Setup = createAdditiveSetup(occs, cam, libraryManager) machineElements = setup.machine.elements machineDimsList: adsk.cam.AdditivePlatformMachineElement = machineElements.itemsByType(adsk.cam.AdditivePlatformMachineElement.staticTypeId()) machineDims = machineDimsList[0] origin = machineDims.origin machineSize = machineDims.size maxPointX = - origin.x + machineSize.x createOrientations(templateLibrary, occs, setup, cam) createAdditiveArrange(setup, cam) curatedOccs = [] for occ in occs: if occ.preciseBoundingBox.minPoint.x <= maxPointX: curatedOccs.append(occ) createSupports(templateLibrary, curatedOccs, setup, cam) ui.activeSelections.clear() ui.activeSelections.add(setup) app.activeViewport.fit() showMessage('Finished.') except: if ui: ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) # Event handler that reacts to when the command is destroyed. This terminates the script if this has not happened before in an exception. class MyCommandDestroyHandler(adsk.core.CommandEventHandler): def __init__(self): super().__init__() def notify(self, args): try: # When the command is done, terminate the script showMessage('Script is terminating.') adsk.terminate() except: if ui: ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) adsk.terminate() # Event handler that reacts when the command definition is executed which # results in the command being created and this event being fired. class MyCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): def __init__(self): super().__init__() def notify(self, args): try: # Get the command that was created. cmd = adsk.core.Command.cast(args.command) onExecute = MyCommandExecuteHandler() cmd.execute.add(onExecute) handlers.append(onExecute) onDestroy = MyCommandDestroyHandler() cmd.destroy.add(onDestroy) handlers.append(onDestroy) except: if ui: ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) def run(context): try: global app, ui # Get the existing command definition or create it if it doesn't already exist. cmdDef = ui.commandDefinitions.itemById('SLAExampleScript') if not cmdDef: cmdDef = ui.commandDefinitions.addButtonDefinition('SLAExampleScript', 'SLA Example Script', 'Sample to demonstrate a workflow for SLA printers.') # Connect to the command created event. onCommandCreated = MyCommandCreatedHandler() cmdDef.commandCreated.add(onCommandCreated) handlers.append(onCommandCreated) # Execute the command definition. cmdDef.execute() # Keeps the script alive until it is terminated by the onDestroy event. adsk.autoTerminate(False) except: if ui: ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) # Given an occurrence, this finds all child occurrences that contain either a # B-Rep or Mesh body. It is recursive, so it will find all occurrences at all levels. def getValidOccurrences(occurrence: adsk.fusion.Occurrence) -> list[adsk.fusion.Occurrence]: result = [] for childOcc in occurrence.childOccurrences: if (childOcc.bRepBodies.count + childOcc.component.meshBodies.count > 0): result.append(childOcc) result.extend(getValidOccurrences(childOcc)) return result def showMessage(message): app.log(message) # Give control back to Fusion, so it can update the UI. adsk.doEvents()