This sample script starts by creating a simple component which is then used to describe a milling workflow. It creates a setup, a few operations, pick some tools from a Fusion sample tool library using loops and queries and ends up post-processing the operations out using an NC Program.
/** * Manufacturing Workflow API Sample * Manufacturing Workflow API Sample This sample script starts by creating a simple component which is then used to describe a milling workflow. It creates a setup, a few operations, pick some tools from a Fusion sample... */ import { adsk } from '@adsk/fusion'; // Params // { // "useCurrentDocument": false, // "saveAsNewDocument": false, // "hubId": "wip1fqaautodesk4298", // "fileURN": "urn:adsk.wipqa:dm.lineage:A2UQE9ECTeqy0RjvgfxYGQ", // "useParentFolder": true, // "outputFolder": "urn:adsk.wipqa:fs.folder:co.mo6KmkbWRAiBs3TZZPrvAw", // "targetProject": "20250120147786973", // "ncProgramFilename": "SampleNCProgram", // "postDescription": "XYZ" // } /////////////// Script Constants /////////////// // We assume we are cutting Aluminum here... // Milling tool library to get tools from const MILLING_TOOL_LIBRARY: string = 'Milling Tools (Metric)' // Some material properties for feed and speed calculation const ALUMINUM_CUTTING_SPEED: number = 300 // mm/min const ALUMINUM_FEED_PER_TOOTH: number = 0.1 // mm/tooth // Some tool preset names, known to exist for the selected tools const ALUMINUM_PRESET_ROUGHING: string = 'alu* rou*' const ALUMINUM_PRESET_FINISHING: string = 'Aluminum - Finishing' /////////////// Useful Enumerators /////////////// // Some tool types used in this script (enumerator) enum ToolType { BULL_NOSE_END_MILL = 'bull nose end mill', BALL_END_MILL = 'ball end mill', FACE_MILL = 'face mill' } enum SetupWCSPoint { TOP_CENTER = 'top center', TOP_XMIN_YMIN = 'top 1', TOP_XMAX_YMIN = 'top 2', TOP_XMIN_YMAX = 'top 3', TOP_XMAX_YMAX = 'top 4', TOP_SIDE_YMIN = 'top side 1', TOP_SIDE_XMAX = 'top side 2', TOP_SIDE_YMAX = 'top side 3', TOP_SIDE_XMIN = 'top side 4', CENTER = 'center', MIDDLE_XMIN_YMIN = 'middle 1', MIDDLE_XMAX_YMIN = 'middle 2', MIDDLE_XMIN_YMAX = 'middle 3', MIDDLE_XMAX_YMAX = 'middle 4', MIDDLE_SIDE_YMIN = 'middle side 1', MIDDLE_SIDE_XMAX = 'middle side 2', MIDDLE_SIDE_YMAX = 'middle side 3', MIDDLE_SIDE_XMIN = 'middle side 4', BOTTOM_CENTER = 'bottom center', BOTTOM_XMIN_YMIN = 'bottom 1', BOTTOM_XMAX_YMIN = 'bottom 2', BOTTOM_XMIN_YMAX = 'bottom 3', BOTTOM_XMAX_YMAX = 'bottom 4', BOTTOM_SIDE_YMIN = 'bottom side 1', BOTTOM_SIDE_XMAX = 'bottom side 2', BOTTOM_SIDE_YMAX = 'bottom side 3', BOTTOM_SIDE_XMIN = 'bottom side 4' } function run(): void { // Read the parameters passed with the script const scriptParameters = JSON.parse(adsk.parameters); if (!scriptParameters) throw Error("Invalid parameters provided."); // Get the Fusion API's application object const app = adsk.core.Application.get(); if (!app) throw Error("No adsk.core.Application."); // Log some information adsk.log(new Date().toString()); adsk.log("UserName: " + app.userName); adsk.log("User: " + app.currentUser.displayName); adsk.log("UserID: " + app.userId); adsk.log("Version: " + app.version); // Create a new empty document //const doc: adsk.core.Document = app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType); const doc = getDocument( app, scriptParameters.useCurrentDocument, scriptParameters.hubId, scriptParameters.fileURN, ); if (!doc) throw Error("Invalid document."); // Get the design document used to create the sample part const design = app.activeProduct as adsk.fusion.Design; // Switch to CAM Environment const camWS = app.userInterface.workspaces.itemById("CAMEnvironment"); camWS?.activate(); // Get the CAM product const products: adsk.core.Products = doc.products; /////////////// Create Sample Part /////////////// const part: adsk.fusion.BRepBody = createSamplePart(design); /////////////// Select Cutting Tools /////////////// adsk.log("Initialazing cam manager...") // Get the tool libraries from the library manager const camManager: adsk.cam.CAMManager = adsk.cam.CAMManager.get(); const libraryManager: adsk.cam.CAMLibraryManager = camManager.libraryManager; const toolLibraries: adsk.cam.ToolLibraries = libraryManager.toolLibraries; let url: adsk.core.URL | null = null; let libUrl: string | null = null; const useHardCodedUrl: boolean = false; if (useHardCodedUrl){ // We could use a library URl directly if we know its address ... libUrl = 'systemlibraryroot://Samples/Milling Tools (Metric).json'; url = adsk.core.URL.create(libUrl); } else{ // ... or we can use the tool library objects // Fusion folder in the tool library const fusionFolder: adsk.core.URL = toolLibraries.urlByLocation(adsk.cam.LibraryLocations.Fusion360LibraryLocation); const fusionLibs: string[] = getLibrariesURLs(toolLibraries, fusionFolder); // Search the required library url in the libraries for (const libUrl of fusionLibs){ if (libUrl.includes(MILLING_TOOL_LIBRARY)){ url = adsk.core.URL.create(libUrl); adsk.log(`Found correct library: ${url}. Terminating loop...`) break; } } } adsk.log("Getting tool library...") // Load the tool library const toolLibrary: adsk.cam.ToolLibrary = toolLibraries.toolLibraryAtURL(url); adsk.log("Successfully got tool library...") // Create some variables to host the milling tools which will be used in the operations let faceTool: adsk.cam.Tool | null = null; let adaptiveTool: adsk.cam.Tool | null = null; let finishingTool: adsk.cam.Tool | null = null; // Read the tool type // For the roughing operations, search for the face mill and the bull nose using a loop for (const tool of toolLibrary) { const toolType: string = (<adsk.cam.StringParameterValue>(tool.parameters.itemByName("tool_type")?.value))?.value; // Select the first face tool found if (toolType === ToolType.FACE_MILL && !faceTool) { faceTool = tool; } // Search for the roughing tool else if (toolType === ToolType.BULL_NOSE_END_MILL && !adaptiveTool) { // Look for a bull nose end mill tool larger or equal to 12mm but less than 14mm const diameter: number = (<adsk.cam.FloatParameterValue>(tool.parameters.itemByName('tool_diameter')?.value))?.value; if (diameter >= 1.2 && diameter < 1.4) { adaptiveTool = tool; } } // Exit when the 2 tools are found if (faceTool && adaptiveTool) { break; } } // Using a query, search for a ball end mill tool with diameter between 6 mm and 10 mm with a minimum flute length of 20.001mm const finishingTools: adsk.cam.Tool[] = getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength(toolLibrary, ToolType.BALL_END_MILL, 0.6, 1, 2.0001); // For this example, we select the first tool found as our finishing tool finishingTool = finishingTools[0]; adsk.log("Creating setup") /////////////// Create Setup /////////////// const cam = doc.products.itemByProductType("CAMProductType") as adsk.cam.CAM; if (!cam) throw Error("File has no CAM Product."); const setups: adsk.cam.Setups = cam.setups; const setupInput: adsk.cam.SetupInput = setups.createInput(adsk.cam.OperationTypes.MillingOperation); adsk.log("Setup input") // Create a list for the models to add to the setup Input const models: adsk.fusion.BRepBody[] = []; // Add the part to the model list models.push(part); // Pass the model list to the setup input setupInput.models = models; // Create the setup and set some properties const setup: adsk.cam.Setup = setups.add(setupInput); setup.name = 'CAM Automation Script Sample'; setup.stockMode = adsk.cam.SetupStockModes.RelativeBoxStock; // Set the offset mode setup.parameters.itemByName('job_stockOffsetMode').expression = "'simple'"; // Set offset stock side setup.parameters.itemByName('job_stockOffsetSides').expression = '0 mm'; // Set offset stock top setup.parameters.itemByName('job_stockOffsetTop').expression = '1 mm'; // Set setup origin (<adsk.cam.StringParameterValue>(setup.parameters.itemByName('wcs_origin_boxPoint')?.value)).value = SetupWCSPoint.TOP_XMIN_YMIN; /////////////// Face Operations /////////////// // Calculate feed and speed for the face operation const toolDiameter: number = (<adsk.cam.FloatParameterValue>(faceTool.parameters.itemByName('tool_diameter')?.value)).value; // cm const numberOfFlutes: number = (<adsk.cam.IntegerParameterValue>(faceTool.parameters.itemByName('tool_numberOfFlutes')?.value)).value; // int const spindleSpeed: number = ALUMINUM_CUTTING_SPEED / Math.PI / (toolDiameter * 10) * 1000; // rpm const cuttingFeedrate: number = spindleSpeed * ALUMINUM_FEED_PER_TOOTH * numberOfFlutes; // mm/min // Create a preset with those calculated feeds const facePreset: adsk.cam.ToolPreset = faceTool.presets.add(); facePreset.name = 'Aluminum (set by script)'; (<adsk.cam.IntegerParameterValue>(facePreset.parameters.itemByName('tool_spindleSpeed')?.value)).value = Math.round(spindleSpeed); facePreset.parameters.itemByName('tool_feedCutting').expression = Math.round(cuttingFeedrate) + ' mm/min'; // Create a face operation input let input: adsk.cam.OperationInput = setup.operations.createInput('face'); input.tool = faceTool; input.toolPreset = facePreset; // Assign created preset input.displayName = 'Face Operation' input.parameters.itemByName('tolerance').expression = '0.01 mm'; input.parameters.itemByName('stepover').expression = '0.75 * tool_diameter'; input.parameters.itemByName('direction').expression = "'climb'" // Determine pass angle along largest part dimension // Get stock box dimensions in cm const stockX: number = (<adsk.cam.FloatParameterValue>(setup.parameters.itemByName('job_stockInfoDimensionX')?.value)).value; const stockY: number = (<adsk.cam.FloatParameterValue>(setup.parameters.itemByName('job_stockInfoDimensionY')?.value)).value; // Determine pass angle to be along largest length (X or Y) of the block if (stockX >= stockY){ input.parameters.itemByName('passAngle').expression = '0 deg'; } else{ input.parameters.itemByName('passAngle').expression = '90 deg'; } // Add the operation to the setup const faceOp: adsk.cam.OperationBase = setup.operations.add(input); /////////////// Adaptive Operations /////////////// input = setup.operations.createInput('adaptive'); input.tool = adaptiveTool; input.displayName = 'Adaptive Roughing'; input.parameters.itemByName('tolerance').expression = '0.1 mm'; input.parameters.itemByName('maximumStepdown').expression = '5 mm'; input.parameters.itemByName('fineStepdown').expression = '0.25 * maximumStepdown'; input.parameters.itemByName('flatAreaMachining').expression = 'false'; // Check if there is a tool preset related to aluminum roughing let presets: adsk.cam.ToolPreset[] = adaptiveTool.presets.itemsByName(ALUMINUM_PRESET_ROUGHING); if (presets.length > 0){ // Select the first preset found const adaptivePreset: adsk.cam.ToolPreset = presets[0]; adsk.log(String(adaptivePreset)); input.toolPreset = adaptivePreset; } // Add the operation to the setup const adaptiveOp: adsk.cam.OperationBase = setup.operations.add(input); /////////////// Finishing Tool Preset /////////////// // Select a tool preset from the finishing tool const finishingPreset: adsk.cam.ToolPreset | null = null; presets = finishingTool.presets.itemsByName(ALUMINUM_PRESET_FINISHING); if (presets.length > 0){ // Use the first aluminum finishing preset found const finishingPreset = presets[0]; } /////////////// Parallel Operations /////////////// input = setup.operations.createInput('parallel') input.tool = finishingTool; input.displayName = 'Parallel Finishing'; input.parameters.itemByName('tolerance').expression = '0.01 mm'; input.parameters.itemByName('cuspHeightStepover').expression = '0.005 mm'; input.parameters.itemByName('boundaryMode').expression = "'selection'"; if (finishingPreset){ // Assign the finishing tool preset input.toolPreset = finishingPreset; } // Add the operation to the setup const parallelOp: adsk.cam.OperationBase = setup.operations.add(input); // Lets use a contour for the sake of demonstration let limitEdge: adsk.fusion.BRepEdge | null = null; for (let i: number = 0; i < part.edges.count; i++){ const e: adsk.fusion.BRepEdge = part.edges.item(i); // This is the inner one: intersection of a plane and a sphere making up a circle if (e.geometry.curveType == adsk.core.Curve3DTypes.Circle3DCurveType){ limitEdge = e; break; } } if (limitEdge){ // Apply the limiting edge to the operation const cadcontours2dParam: adsk.cam.CadContours2dParameterValue = (<adsk.cam.CadContours2dParameterValue>(parallelOp.parameters.itemByName('machiningBoundarySel')?.value)); const chains: adsk.cam.CurveSelections = cadcontours2dParam.getCurveSelections(); const chain: adsk.cam.ChainSelection = chains.createNewChainSelection(); chain.inputGeometry = [limitEdge]; cadcontours2dParam.applyCurveSelections(chains); } /////////////// Steep And Shallow Operations /////////////// // Create folder for finishing operations that require Machining Extension const operationInput: adsk.cam.OperationInput = setup.operations.createInput('folder'); operationInput.displayName = 'Machining Extension Required'; const folder = setup.operations.add(operationInput) as adsk.cam.CAMFolder; // Create steep and shallow operation in the folder input = setup.operations.createInput('steep_and_shallow'); input.tool = finishingTool; input.displayName = 'Steep and Shallow Finishing'; input.parameters.itemByName('tolerance').expression = '0.01 mm'; input.parameters.itemByName('useAvoidFlats').expression = 'true'; input.parameters.itemByName('cuspHeightStepdown').expression = '0.005 mm'; input.parameters.itemByName('cuspHeightStepover').expression = 'cuspHeightStepdown'; input.parameters.itemByName('spiral').expression = 'true'; input.parameters.itemByName('shallowSpiral').expression = 'true'; input.parameters.itemByName('offsetSmoothing').expression = 'true'; // Assign the finishing tool preset if (finishingPreset){ input.toolPreset = finishingPreset; } // Add the operation to the folder const steepAndShallowOp: adsk.cam.OperationBase = folder.operations.add(input) // Check if this toolpath is generatable as "steep_and_shallow" requires the manufacturing extension let isSteepAndShallowGeneratable: boolean = false; for (const op of setup.operations.compatibleStrategies){ if (op.name == 'steep_and_shallow'){ if (op.isGenerationAllowed){ // If isGenerationAllowed is False, the extension isn't active, preventing generation of the steep_and_shallow operation isSteepAndShallowGeneratable = true; } break; } } /////////////// Generate Operations /////////////// // Include the valid operations to generate const operations = adsk.core.ObjectCollection.create(); operations.add(faceOp); operations.add(adaptiveOp); operations.add(parallelOp); if (isSteepAndShallowGeneratable){ operations.add(steepAndShallowOp); } // create progress bar adsk.log('Generating operations...') // Generate the valid operations const gtf: adsk.cam.GenerateToolpathFuture = cam.generateToolpath(operations); const total = gtf.numberOfOperations; // Initialize the last logged progress percentage let lastLoggedProgress = 0; // Wait for the generation to be finished and update progress bar while (!gtf.isGenerationCompleted) { // Calculate progress and update progress bar let completed = gtf.numberOfCompleted; let progress = Math.round(completed * 100 / total); // Determine the current progress percentage rounded to the nearest 20% let progressPercentage = Math.floor(progress / 20) * 20; // Log progress only if it has advanced to the next percentage step if (progressPercentage > lastLoggedProgress) { adsk.log(String(progressPercentage) + '%'); lastLoggedProgress = progressPercentage; } } adsk.log("Generation completed..."); /////////////// NC Program And Post-processing /////////////// // Get the post library from library manager const postLibrary: adsk.cam.PostLibrary = libraryManager.postLibrary; // Query post library to get postprocessor list const postQuery: adsk.cam.PostConfigurationQuery = postLibrary.createQuery(adsk.cam.LibraryLocations.Fusion360LibraryLocation); postQuery.vendor = "Autodesk"; postQuery.capability = adsk.cam.PostCapabilities.Milling; const postConfigs: adsk.cam.PostConfiguration[] = postQuery.execute(); // Find the "XYZ" post in the post library const postRequired = scriptParameters.postDescription let postConfig: adsk.cam.PostConfiguration | null = null for (const config of postConfigs){ if (config.description == postRequired){ postConfig = config break; } } // Create NCProgramInput object const ncInput: adsk.cam.NCProgramInput = cam.ncPrograms.createInput(); ncInput.displayName = 'NC Program Sample'; // Change some nc program parameters... const ncParameters: adsk.cam.CAMParameters = ncInput.parameters; // Changing the NC Program file name (<adsk.cam.StringParameterValue>(ncParameters.itemByName("nc_program_filename")?.value)).value = scriptParameters.ncProgramFilename; (<adsk.cam.BooleanParameterValue>(ncParameters.itemByName('nc_program_openInEditor')?.value)).value = false; // Setting the output folder parameters (<adsk.cam.BooleanParameterValue>(ncParameters.itemByName("nc_program_postToFusionTeam")?.value)).value = true; // Setting Fusion Teams Output Folder Parameter (<adsk.cam.StringParameterValue>(ncParameters.itemByName("nc_program_internal_fusion_team_output_folder")?.value)). value = getFolderId( app, scriptParameters.useParentFolder, scriptParameters.outputFolder); (<adsk.cam.StringParameterValue>(ncParameters.itemByName("nc_program_fusion_team_target_project_id")?.value)).value = scriptParameters.targetProject; // Select the operations to generate (we skip steep_and_shallow here) ncInput.operations = [faceOp, adaptiveOp, parallelOp] // Add a new ncprogram from the ncprogram input const newProgram: adsk.cam.NCProgram = cam.ncPrograms.add(ncInput) // Set post processor newProgram.postConfiguration = postConfig; // Change some post parameter const postParameters: adsk.cam.CAMParameters = newProgram.postParameters; // NcProgram parameters are passed as theyare to the postprocessor (they have no units) (<adsk.cam.FloatParameterValue>(postParameters.itemByName('builtin_tolerance')?.value)).value = 0.01; (<adsk.cam.FloatParameterValue>(postParameters.itemByName('builtin_minimumChordLength')?.value)).value = 0; // Update/apply post parameters newProgram.updatePostParameters(postParameters) // Set post options, by default post process only valid operations containing toolpath data const postOptions = adsk.cam.NCProgramPostProcessOptions.create() // Post-process newProgram.postProcess(postOptions) // saveDocument( // doc, // scriptParameters.saveAsNewDocument, // "Manufacturing Workflow Sample File models not assigned" , // doc.dataFile.parentFolder, // ); } function createSamplePart(design: adsk.fusion.Design): adsk.fusion.BRepBody{ // Creates the sample part for this script // The sample part is a box containing a concave side generated by subtracting the intersection from a sphere. // The lower face of the box is at Z=0 and we position the sphere above but intersecting with the box upper face. // Fusion's default behaviour is now to ground the first component of an assembly to the parent. // This can be overriden, see Preferences ->General ->Design ->Assemblies ->First component grounded to parent // However if we make the first component the box, we can move the sphere without changing anything const box: adsk.fusion.BRepBody = createBox(design, 22, 15, 5) // 1st component occurrence const sphere: adsk.fusion.BRepBody = createSphere(design, 7.5) // 2nd component occurrence // Get the root component const rootComp: adsk.fusion.Component = design.rootComponent const occs: adsk.fusion.Occurrences = rootComp.occurrences // Get the second Occurrence (sphere) as the first occurrence (box) may be grounded to the parent const occ: adsk.fusion.Occurrence = occs.item(1) const mat: adsk.core.Matrix3D = occ.transform2 // Matrix translation, moving the sphere up by 10 units along Z axis mat.translation = adsk.core.Vector3D.create(0, 0, 10) // Set transform occ.transform2 = mat // Snapshot - Determining the position is important!!! design.snapshots.add() // Cut the sphere/box intersection from the box to leave a concave face const part: adsk.fusion.BRepBody = getBodyFromBooleanOperation(design, box, sphere) return part } function createBox(design: adsk.fusion.Design, sizeX: number, sizeY: number, sizeZ: number): adsk.fusion.BRepBody{ // Creates a sample box // const component: adsk.fusion.Component = design.rootComponent // Create sketch const sketches: adsk.fusion.Sketches = component.sketches const sketch: adsk.fusion.Sketch = sketches.add(component.xYConstructionPlane) const lines: adsk.fusion.SketchLines = sketch.sketchCurves.sketchLines lines.addTwoPointRectangle(adsk.core.Point3D.create(-sizeX / 2, -sizeY / 2, 0), adsk.core.Point3D.create(sizeX / 2, sizeY / 2, 0)) const prof: adsk.fusion.Profile = sketch.profiles.item(0) const extrudes: adsk.fusion.ExtrudeFeatures = component.features.extrudeFeatures const distance = adsk.core.ValueInput.createByReal(sizeZ) const ext: adsk.fusion.ExtrudeFeature = extrudes.addSimple(prof, distance, adsk.fusion.FeatureOperations.NewComponentFeatureOperation) return ext.bodies.item(0) } function createSphere(design: adsk.fusion.Design, radius: number): adsk.fusion.BRepBody{ // Creates a sample sphere // const component: adsk.fusion.Component = design.rootComponent // Create a new sketch on the xy plane. const sketches: adsk.fusion.Sketches = component.sketches const xyPlane: adsk.fusion.ConstructionPlane = component.xYConstructionPlane const sketch: adsk.fusion.Sketch = sketches.add(xyPlane) // Draw a circle. const circles: adsk.fusion.SketchCircles = sketch.sketchCurves.sketchCircles circles.addByCenterRadius(adsk.core.Point3D.create(0, 0, 0), radius) // Draw a line to use as the axis of revolution. const lines: adsk.fusion.SketchLines = sketch.sketchCurves.sketchLines const axisLine: adsk.fusion.SketchLine = lines.addByTwoPoints(adsk.core.Point3D.create(-radius, 0, 0), adsk.core.Point3D.create(radius, 0, 0)) // Get the profile defined by half of the circle. const prof: adsk.fusion.Profile = sketch.profiles.item(0) // Create an revolution input for a revolution while specifying the profile and that a new component is to be created const revolves: adsk.fusion.RevolveFeatures = component.features.revolveFeatures const revInput: adsk.fusion.RevolveFeatureInput = revolves.createInput(prof, axisLine, adsk.fusion.FeatureOperations.NewComponentFeatureOperation) // Define that the extent is an angle of 2*pi to get a sphere const angle = adsk.core.ValueInput.createByReal(2*Math.PI) revInput.setAngleExtent(false, angle) // Create the extrusion. const ext: adsk.fusion.RevolveFeature = revolves.add(revInput) return ext.bodies.item(0) } function getBodyFromBooleanOperation(design: adsk.fusion.Design, body1: adsk.fusion.BRepBody, body2: adsk.fusion.BRepBody): adsk.fusion.BRepBody{ // Creates a boolean operation between two bodies // const model: adsk.fusion.Component = design.activeComponent const features: adsk.fusion.Features = model.features // Create a collection and add our sphere let bodyCollection = adsk.core.ObjectCollection.create() bodyCollection.add(body2) // Create a CombineFeatureInput using our box (body1), sphere (bodyCollection) specifying a 'cut' operation const combineFeatures: adsk.fusion.CombineFeatures = features.combineFeatures let combineFeatureInput: adsk.fusion.CombineFeatureInput = combineFeatures.createInput(body1, bodyCollection) combineFeatureInput.operation = adsk.fusion.FeatureOperations.CutFeatureOperation combineFeatureInput.isKeepToolBodies = false combineFeatureInput.isNewComponent = false // Generate our combined feature const returnValue: adsk.fusion.CombineFeature = combineFeatures.add(combineFeatureInput) const part: adsk.fusion.BRepBody = returnValue.bodies[0]; return part } function getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength( toolLibrary: adsk.cam.ToolLibrary, tooltype: string, minDiameter: number, maxDiameter: number, minimumFluteLength: number | null = null): adsk.cam.Tool[]{ // Return a list of tools that satisfy the search // const query: adsk.cam.ToolQuery = toolLibrary.createQuery() // Set the search critera query.criteria.add('tool_type', adsk.core.ValueInput.createByString(tooltype)) query.criteria.add('tool_diameter.min', adsk.core.ValueInput.createByReal(minDiameter)) query.criteria.add('tool_diameter.max', adsk.core.ValueInput.createByReal(maxDiameter)) if (minimumFluteLength){ query.criteria.add('tool_fluteLength.min', adsk.core.ValueInput.createByReal(minimumFluteLength)) } // Get query results const results: adsk.cam.ToolQueryResult[] = query.execute() // Get the tools from the query const tools: adsk.cam.Tool[] = [] for (const result of results){ // Results have a tool, url, toollibrary and the index of the tool in that library: we just return the tool here tools.push(result.tool) } return tools } function getLibrariesURLs(libraries: adsk.cam.ToolLibraries, url: adsk.core.URL): string[]{ // Return the list of libraries' URLs for the specified libraries // let urls: string[] = [] const libs: adsk.core.URL[] = libraries.childAssetURLs(url) for (const lib of libs) { urls.push(lib.toString()) } const folders: adsk.core.URL[] = libraries.childFolderURLs(url); for (const folder of folders) { urls = urls.concat(getLibrariesURLs(libraries, folder)) } return urls } function getDocument( app: adsk.core.Application, useCurrentDocument: boolean, hubId: string, fileURN: string, ): adsk.core.Document { if (useCurrentDocument === true) { adsk.log(`Using currently open document: ${app.activeDocument.name}.`); return app.activeDocument; } if (hubId) { // Possible hubId formats: base64 encoded string, or business:<id>, // or personal:<id> (deprecated) const hub = app.data.dataHubs.itemById(hubId) || app.data.dataHubs.itemById(`a.${adsk.btoa(`business:${hubId}`, true)}`) || app.data.dataHubs.itemById(`a.${adsk.btoa(`personal:${hubId}`, true)}`); if (!hub) throw Error(`Hub with id ${hubId} not found.`); adsk.log(`Setting hub: ${hub.name}.`); app.data.activeHub = hub; } const file = app.data.findFileById(fileURN); if (!file) throw Error(`File not found ${fileURN}.`); adsk.log(`Opening ${file.name}`); const document = app.documents.open(file, true); if (!document) throw Error(`Cannot open file ${file.name}.`); return document; } function wait(ms: number) { const start = new Date().getTime(); while (new Date().getTime() - start < ms) adsk.doEvents(); } function saveDocument( doc: adsk.core.Document, saveAsNewDocument: boolean, message: string, destinationFolder: adsk.core.DataFolder, ): boolean { if (saveAsNewDocument) { adsk.log("Saving as new document."); try { destinationFolder.parentProject; } catch (e) { adsk.log( "Destination folder is not in a project, setting folder to Fusion Automation Service project.", ); destinationFolder = defaultFolder( doc.parent, "Fusion Automation Service", ); } if ( doc.saveAs(doc.name + " Additive Ready", destinationFolder, message, "") ) { adsk.log("Document saved successfully."); return true; } else { adsk.log("Document failed to save."); return false; } } if (!doc.isModified) { adsk.log("Document not modified, not saving."); return true; } adsk.log(`Saving with message: "${message}".`); if (doc.save(message)) { adsk.log("Document saved successfully."); return true; } else { adsk.log("Document failed to save."); return false; } } function defaultFolder(app: adsk.core.Application, defaultProjectName: string) { const projects = app.data.activeHub.dataProjects; if (!projects) throw Error("Unable to get active hub's projects."); for (let i = 0; i < projects.count; ++i) { const project = projects.item(i)!; if (project.name === defaultProjectName) { return project.rootFolder; } } adsk.log(`Creating new project: ${defaultProjectName}`); const project = projects.add(defaultProjectName); if (!project) throw Error("Unable to create new project."); return project.rootFolder; } function getFolderId(app: adsk.core.Application, useParentFolder: boolean, outputFolder: string,): string { if (useParentFolder) { return app.activeDocument.dataFile.parentFolder.id; } return outputFolder; } run();
#include <Core/CoreAll.h> #include <Fusion/FusionAll.h> #include <Cam/CamAll.h> #include <algorithm> using namespace adsk::core; using namespace adsk::fusion; using namespace adsk::cam; // Constants used in the script // We assume we are cutting Aluminum here... // Milling tool library to get tools from const std::string MILLING_TOOL_LIBRARY = "Milling Tools (Metric)"; // Material properties for feed and speed calculation const int ALUMINUM_CUTTING_SPEED = 300; // mm/min const double ALUMINUM_FEED_PER_TOOTH = 0.1; // mm/tooth // Tool preset names (which we know exists for the selected tools) const std::string ALUMINUM_PRESET_ROUGHING = "alu* rou*"; const std::string ALUMINUM_PRESET_FINISHING = "Aluminum - Finishing"; // Tool types used in this script const std::string BULL_NOSE_END_MILL = "bull nose end mill"; const std::string BALL_END_MILL = "ball end mill"; const std::string FACE_MILL = "face mill"; // Setup work coordinate system (WCS) location const std::string TOP_CENTER = "top center"; const std::string TOP_XMIN_YMIN = "top 1"; const std::string TOP_XMAX_YMIN = "top 2"; const std::string TOP_XMIN_YMAX = "top 3"; const std::string TOP_XMAX_YMAX = "top 4"; const std::string TOP_SIDE_YMIN = "top side 1"; const std::string TOP_SIDE_XMAX = "top side 2"; const std::string TOP_SIDE_YMAX = "top side 3"; const std::string TOP_SIDE_XMIN = "top side 4"; const std::string CENTER = "center"; const std::string MIDDLE_XMIN_YMIN = "middle 1"; const std::string MIDDLE_XMAX_YMIN = "middle 2"; const std::string MIDDLE_XMIN_YMAX = "middle 3"; const std::string MIDDLE_XMAX_YMAX = "middle 4"; const std::string MIDDLE_SIDE_YMIN = "middle side 1"; const std::string MIDDLE_SIDE_XMAX = "middle side 2"; const std::string MIDDLE_SIDE_YMAX = "middle side 3"; const std::string MIDDLE_SIDE_XMIN = "middle side 4"; const std::string BOTTOM_CENTER = "bottom center"; const std::string BOTTOM_XMIN_YMIN = "bottom 1"; const std::string BOTTOM_XMAX_YMIN = "bottom 2"; const std::string BOTTOM_XMIN_YMAX = "bottom 3"; const std::string BOTTOM_XMAX_YMAX = "bottom 4"; const std::string BOTTOM_SIDE_YMIN = "bottom side 1"; const std::string BOTTOM_SIDE_XMAX = "bottom side 2"; const std::string BOTTOM_SIDE_YMAX = "bottom side 3"; const std::string BOTTOM_SIDE_XMIN = "bottom side 4"; const double PI = 3.14159265358979323846; std::vector<std::string> getLibrariesURLs(Ptr<ToolLibraries> libraries, Ptr<URL> url); std::vector<Ptr<Tool>> getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength( Ptr<ToolLibrary> toolLibrary, std::string tooltype, double minDiameter, double maxDiameter, double minimumFluteLength = 0.0); Ptr<BRepBody> createSamplePart(Ptr<Design> design); extern "C" XI_EXPORT bool run(const char* context) { // Initialisation Ptr<Application> app = Application::get(); Ptr<UserInterface> ui = app->userInterface(); // Create a new empty document Ptr<Document> doc = app->documents()->add(DocumentTypes::FusionDesignDocumentType); // Get the design document used to create the sample part Ptr<Design> design = app->activeProduct(); // Switch to manufacturing space Ptr<Workspace> camWS = ui->workspaces()->itemById("CAMEnvironment"); camWS->activate(); // Get the CAM product Ptr<Products> products = doc->products(); // Create sample part Ptr<BRepBody> part = createSamplePart(design); // Select cutting tools // Get the tool libraries from the library manager Ptr<CAMManager> camManager = CAMManager::get(); Ptr<CAMLibraryManager> libraryManager = camManager->libraryManager(); Ptr<ToolLibraries> toolLibraries = libraryManager->toolLibraries(); Ptr<URL> url; const bool useHardCodedUrl = false; if (useHardCodedUrl) { // We can use a library URl directly if we know its address std::string libUrl = "systemlibraryroot://Samples/Milling Tools (Metric).json"; url = URL::create(libUrl); } else { // Or we can use the tool library objects fusion folder in the tool library Ptr<URL> fusionFolder = toolLibraries->urlByLocation(LibraryLocations::Fusion360LibraryLocation); std::vector<std::string> fusionLibs = getLibrariesURLs(toolLibraries, fusionFolder); // Search the required library url in the libraries for (std::string& libUrl : fusionLibs) { if (libUrl.find(MILLING_TOOL_LIBRARY) != std::string::npos) { url = URL::create(libUrl); break; } } } // Load the tool library Ptr<ToolLibrary> toolLibrary = toolLibraries->toolLibraryAtURL(url); if (!toolLibrary) { ui->messageBox("Failed to load tool library"); return false; } // Create variables to host the milling tools to be used in the operations Ptr<Tool> faceTool; Ptr<Tool> adaptiveTool; Ptr<Tool> finishingTool; // Search for the face mill and the bull nose using a loop for the roughing operations for (Ptr<Tool> tool : toolLibrary) { // Read the tool type Ptr<ChoiceParameterValue> toolTypeParameter = tool->parameters()->itemByName("tool_type")->value(); std::string toolType = toolTypeParameter->value(); if (toolType == FACE_MILL && !faceTool) { // Select the first face tool found faceTool = tool; } else if (toolType == BULL_NOSE_END_MILL && !adaptiveTool) { // Search the roughing tool // We look for a bull nose end mill tool larger or equal to 12mm but less than 14mm Ptr<FloatParameterValue> diameterParameter = tool->parameters()->itemByName("tool_diameter")->value(); double diameter = diameterParameter->value(); if (diameter >= 1.2 && diameter < 1.4) { adaptiveTool = tool; } } // Exit when the 2 tools are found if (faceTool && adaptiveTool) { break; } } if (!faceTool) { ui->messageBox("No face mill tool found"); return false; } if (!adaptiveTool) { ui->messageBox("No bull nose end mill tool found"); return false; } // Using a query, search for a ball end mill tool having diameter between 6 mm and 10 mm and a minimum flute length // of 20.001mm std::vector<Ptr<Tool>> finishingTools = getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength(toolLibrary, BALL_END_MILL, 0.6, 1, 2.0001); // For this example, we select the first tool found as our finishing tool if (finishingTools.size() == 0) { ui->messageBox("No ball end mill tool found"); return false; } finishingTool = finishingTools[0]; // Create a setup Ptr<CAM> cam = products->itemByProductType("CAMProductType"); Ptr<Setups> setups = cam->setups(); Ptr<SetupInput> setupInput = setups->createInput(OperationTypes::MillingOperation); // Create a list for the models to add to the setup Input std::vector<Ptr<Base>> models; // Add the part to the model list models.push_back(part); // Pass the model list to the setup input setupInput->models(models); // Create the setup and set some properties Ptr<Setup> setup = setups->add(setupInput); setup->name("CAM Automation Script Sample"); setup->stockMode(SetupStockModes::RelativeBoxStock); // Set the offset mode setup->parameters()->itemByName("job_stockOffsetMode")->expression("\"simple\""); // Set the offset stock side setup->parameters()->itemByName("job_stockOffsetSides")->expression("0 mm"); // Set the offset stock top setup->parameters()->itemByName("job_stockOffsetTop")->expression("1 mm"); // Set the setup origin Ptr<ChoiceParameterValue> wcs_origin = setup->parameters()->itemByName("wcs_origin_boxPoint")->value(); wcs_origin->value(TOP_XMIN_YMIN); // Face operations // Calculate the feed and speed for the face operation Ptr<FloatParameterValue> toolDiameterParameter = faceTool->parameters()->itemByName("tool_diameter")->value(); double toolDiameter = toolDiameterParameter->value(); // cm Ptr<IntegerParameterValue> numberOfFlutesParameter = faceTool->parameters()->itemByName("tool_numberOfFlutes")->value(); int numberOfFlutes = numberOfFlutesParameter->value(); // int double spindleSpeed = ALUMINUM_CUTTING_SPEED / PI / (toolDiameter * 10) * 1000; // rpm double cuttingFeedrate = spindleSpeed * ALUMINUM_FEED_PER_TOOTH * numberOfFlutes; // mm/min // Create a preset with those calculated feeds Ptr<ToolPreset> facePreset = faceTool->presets()->add(); facePreset->name("Aluminum (set by script)"); Ptr<FloatParameterValue> tool_spindleSpeed = facePreset->parameters()->itemByName("tool_spindleSpeed")->value(); tool_spindleSpeed->value(int(spindleSpeed)); facePreset->parameters() ->itemByName("tool_feedCutting") ->expression(std::to_string(int(cuttingFeedrate)) + " mm/min"); // Create a face operation input Ptr<OperationInput> input = setup->operations()->createInput("face"); input->tool(faceTool); input->toolPreset(facePreset); // Assign created preset input->displayName("Face Operation"); input->parameters()->itemByName("tolerance")->expression("0.01 mm"); input->parameters()->itemByName("stepover")->expression("0.75 * tool_diameter"); input->parameters()->itemByName("direction")->expression("\"climb\""); // Determine the pass angle along the largest part dimension // Get the stock box dimensions in cm Ptr<FloatParameterValue> stockXParameter = setup->parameters()->itemByName("job_stockInfoDimensionX")->value(); double stockX = stockXParameter->value(); Ptr<FloatParameterValue> stockYParameter = setup->parameters()->itemByName("job_stockInfoDimensionY")->value(); double stockY = stockYParameter->value(); // Determine the pass angle to be along the largest length (X or Y) of the block if (stockX >= stockY) { input->parameters()->itemByName("passAngle")->expression("0 deg"); } else { input->parameters()->itemByName("passAngle")->expression("90 deg"); } // Add the operation to the setup Ptr<OperationBase> faceOp = setup->operations()->add(input); // Adaptive operations input = setup->operations()->createInput("adaptive"); input->tool(adaptiveTool); input->displayName("Adaptive Roughing"); input->parameters()->itemByName("tolerance")->expression("0.1 mm"); input->parameters()->itemByName("maximumStepdown")->expression("5 mm"); input->parameters()->itemByName("fineStepdown")->expression("0.25 * maximumStepdown"); input->parameters()->itemByName("flatAreaMachining")->expression("false"); // Look for a tool preset related to aluminum roughing std::vector<Ptr<ToolPreset>> presets = adaptiveTool->presets()->itemsByName(ALUMINUM_PRESET_ROUGHING); if (presets.size() > 0) { // We select and use the first preset found Ptr<ToolPreset> adaptivePreset = presets[0]; input->toolPreset(adaptivePreset); } // Add the operation to the setup Ptr<OperationBase> adaptiveOp = setup->operations()->add(input); // Finishing tool preset // Get a tool preset from the finishing tool Ptr<ToolPreset> finishingPreset; presets = finishingTool->presets()->itemsByName(ALUMINUM_PRESET_FINISHING); if (presets.size() > 0) { // Use the first aluminum finishing preset found finishingPreset = presets[0]; } // Parallel operations input = setup->operations()->createInput("parallel"); input->tool(finishingTool); input->displayName("Parallel Finishing"); input->parameters()->itemByName("tolerance")->expression("0.01 mm"); input->parameters()->itemByName("cuspHeightStepover")->expression("0.005 mm"); input->parameters()->itemByName("boundaryMode")->expression("\"selection\""); if (finishingPreset) { // Assign the finishig tool preset input->toolPreset(finishingPreset); } // Add the operation to the setup Ptr<OperationBase> parallelOp = setup->operations()->add(input); // Use a contour for the sake of demonstration Ptr<BRepEdge> limitEdge; for (Ptr<BRepEdge> e : part->edges()) { // This is the inner one: intersection of a plane and a sphere making up a circle if (e->geometry()->curveType() == Curve3DTypes::Circle3DCurveType) { limitEdge = e; break; } } if (limitEdge) { // Apply the limits edge to the operation Ptr<CadContours2dParameterValue> cadcontours2dParam = parallelOp->parameters()->itemByName("machiningBoundarySel")->value(); Ptr<CurveSelections> chains = cadcontours2dParam->getCurveSelections(); Ptr<ChainSelection> chain = chains->createNewChainSelection(); chain->inputGeometry({limitEdge}); cadcontours2dParam->applyCurveSelections(chains); } // Steep and shallow operations // Create a folder for finishing operations that require Machining Extension Ptr<OperationInput> operationInput = setup->operations()->createInput("folder"); operationInput->displayName("Machining Extension Required"); Ptr<CAMFolder> folder = setup->operations()->add(operationInput); // Create a steep and shallow operation in the folder input = setup->operations()->createInput("steep_and_shallow"); input->tool(finishingTool); input->displayName("Steep and Shallow Finishing"); input->parameters()->itemByName("tolerance")->expression("0.01 mm"); input->parameters()->itemByName("useAvoidFlats")->expression("true"); input->parameters()->itemByName("cuspHeightStepdown")->expression("0.005 mm"); input->parameters()->itemByName("cuspHeightStepover")->expression("cuspHeightStepdown"); input->parameters()->itemByName("spiral")->expression("true"); input->parameters()->itemByName("shallowSpiral")->expression("true"); input->parameters()->itemByName("offsetSmoothing")->expression("true"); if (finishingPreset) { // Assign the finishig tool preset input->toolPreset(finishingPreset); } // Add the operation to the folder Ptr<OperationBase> steepAndShallowOp = folder->operations()->add(input); // Check whether this toolpath is generatable ("steep_and_shallow" required the manufacturing extension) bool isSteepAndShallowGeneratable = false; for (Ptr<OperationStrategy> op : setup->operations()->compatibleStrategies()) { if (op->name() == "steep_and_shallow") { if (op->isGenerationAllowed()) { // isGenerationAllowed will be false if the extension is not active thus preventing the // steep_and_shallow operation isSteepAndShallowGeneratable = true; } break; } } // Generate operations // List the valid operations to generate Ptr<ObjectCollection> operations = ObjectCollection::create(); operations->add(faceOp); operations->add(adaptiveOp); operations->add(parallelOp); if (isSteepAndShallowGeneratable) { operations->add(steepAndShallowOp); } // Create progress bar Ptr<ProgressDialog> progressDialog = ui->createProgressDialog(); progressDialog->isCancelButtonShown(false); progressDialog->show("Generating operations->()..", "%p%", 0, 100); adsk::doEvents(); // Allow Fusion to update so that the progressDialog updates nicely // Generate the valid operations Ptr<GenerateToolpathFuture> gtf = cam->generateToolpath(operations); // Wait for the generation to be finished and update the progress bar while (!gtf->isGenerationCompleted()) { // Calculate progress and update the progress bar int total = gtf->numberOfOperations(); int completed = gtf->numberOfCompleted(); int progress = int(completed * 100 / total); progressDialog->progressValue(progress); adsk::doEvents(); // Allow Fusion to update so that the screen does not freeze } // Generation done progressDialog->progressValue(100); progressDialog->hide(); // NC Program and post-processing // Get the post library from the library manager Ptr<PostLibrary> postLibrary = libraryManager->postLibrary(); // Query the post library to get the postprocessor list Ptr<PostConfigurationQuery> postQuery = postLibrary->createQuery(LibraryLocations::Fusion360LibraryLocation); postQuery->vendor("Autodesk"); postQuery->capability(PostCapabilities::Milling); std::vector<Ptr<PostConfiguration>> postConfigs = postQuery->execute(); // Find the "XYZ" post in the post library and import it to the local library Ptr<URL> importedURL; for (Ptr<PostConfiguration> config : postConfigs) { if (config->description() == "XYZ") { Ptr<URL> _url = URL::create("user://"); importedURL = postLibrary->importPostConfiguration(config, _url, "NCProgramSamplePost.cps"); } } // Get the imported local post config Ptr<PostConfiguration> postConfig = postLibrary->postConfigurationAtURL(importedURL); // Create the NCProgramInput object Ptr<NCProgramInput> ncInput = cam->ncPrograms()->createInput(); ncInput->displayName("NC Program Sample"); // Change some nc program parameters... Ptr<CAMParameters> ncParameters = ncInput->parameters(); std::string programName = "NCProgramSample"; Ptr<StringParameterValue> nc_program_filename = ncParameters->itemByName("nc_program_filename")->value(); nc_program_filename->value(programName); Ptr<BooleanParameterValue> nc_program_openInEditor = ncParameters->itemByName("nc_program_openInEditor")->value(); nc_program_openInEditor->value(true); // Set the temporary directory as the output directory std::string outputFolder = cam->temporaryFolder(); Ptr<StringParameterValue> nc_program_output_folder = ncParameters->itemByName("nc_program_output_folder")->value(); nc_program_output_folder->value(outputFolder); // Select the operations to generate (we skip steep_and_shallow here) ncInput->operations({faceOp, adaptiveOp, parallelOp}); // Add a new ncprogram from the ncprogram input Ptr<NCProgram> newProgram = cam->ncPrograms()->add(ncInput); // Set the post processor newProgram->postConfiguration(postConfig); // Change some of the post parameters Ptr<CAMParameters> postParameters = newProgram->postParameters(); Ptr<FloatParameterValue> builtin_tolerance = postParameters->itemByName("builtin_tolerance")->value(); builtin_tolerance->value(0.02); // NcProgram parameter is passed unchanged to the postprocessor (it has no units) Ptr<FloatParameterValue> builtin_minimumChordLength = postParameters->itemByName("builtin_minimumChordLength")->value(); builtin_minimumChordLength->value(0.33); // NcProgram parameter is passed unchanged (it has no units) // Update/apply post parameters newProgram->updatePostParameters(postParameters); // Set post options, by default post process only valid operations containing toolpath data Ptr<NCProgramPostProcessOptions> postOptions = NCProgramPostProcessOptions::create(); // postOptions.PostProcessExecutionBehaviors = // adsk.cam.PostProcessExecutionBehaviors.PostProcessExecutionBehavior_PostAll // Post-process newProgram->postProcess(postOptions); // Show the output file ui->messageBox( "Post processing is complete. The results have been written to:\n" + outputFolder + "/" + programName + ".csv"); return true; } // Return the list of libraries' URLs for the specified libraries std::vector<std::string> getLibrariesURLs(Ptr<ToolLibraries> libraries, Ptr<URL> url) { std::vector<std::string> urls; std::vector<Ptr<URL>> libs = libraries->childAssetURLs(url); for (auto& elem : libs) { urls.push_back(elem->toString()); } for (Ptr<URL> folder : libraries->childFolderURLs(url)) { std::vector<std::string> folderUrls = getLibrariesURLs(libraries, folder); urls.insert(urls.end(), folderUrls.begin(), folderUrls.end()); } return urls; } // Return a list of tools that fits the search std::vector<Ptr<Tool>> getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength( Ptr<ToolLibrary> toolLibrary, std::string tooltype, double minDiameter, double maxDiameter, double minimumFluteLength) { Ptr<ToolQuery> query = toolLibrary->createQuery(); // Set the search critera query->criteria()->add("tool_type", ValueInput::createByString(tooltype)); query->criteria()->add("tool_diameter.min", ValueInput::createByReal(minDiameter)); query->criteria()->add("tool_diameter.max", ValueInput::createByReal(maxDiameter)); if (minimumFluteLength) { query->criteria()->add("tool_fluteLength.min", ValueInput::createByReal(minimumFluteLength)); } // Get query results std::vector<Ptr<ToolQueryResult>> results = query->execute(); // Get the tools from the query std::vector<Ptr<Tool>> tools; // Results have a tool, url, toollibrary and the index of the tool in that library: we just return the tool here for (auto& elem : results) { tools.push_back(elem->tool()); } return tools; } // CAD creation functions Ptr<BRepBody> createBox(Ptr<Design> design, double sizeX, double sizeY, double sizeZ); Ptr<BRepBody> createSphere(Ptr<Design> design, double radius); Ptr<BRepBody> getBodyFromBooleanOperation(Ptr<Design> design, Ptr<BRepBody> body1, Ptr<BRepBody> body2); // Create the sample part for this script Ptr<BRepBody> createSamplePart(Ptr<Design> design) { // The sample part is a box containing a concave side generated by subtracting the intersection from a sphere. // The lower face of the box is at Z = 0 and we position the sphere above but intersecting with the box upper face. // Fusion's default behaviour is now to ground the first component of an assembly to the parent. // This can be overriden, see Preferences->General->Design->Assemblies->First component grounded to parent. // However if we make the first component the box, we can move the sphere without changing anything. Ptr<BRepBody> box = createBox(design, 22, 15, 5); Ptr<BRepBody> sphere = createSphere(design, 7.5); // Get the root component Ptr<Component> rootComp = design->rootComponent(); Ptr<Occurrences> occs = rootComp->occurrences(); // Get the second Occurrence(sphere) as the first occurrence(box) may be grounded to the parent Ptr<Occurrence> occ = occs->item(1); Ptr<Matrix3D> mat = occ->transform(); // Matrix translation, moving the sphere up by 10 units along Z axis Ptr<Vector3D> origin = Vector3D::create(0, 0, 10); mat->translation(origin); // Set the transform occ->transform(mat); // Snapshot - Determining the position is important!!! design->snapshots()->add(); // Cut the sphere / box intersection from the box to leave a concave face Ptr<BRepBody> part = getBodyFromBooleanOperation(design, box, sphere); return part; } // Create a sample box Ptr<BRepBody> createBox(Ptr<Design> design, double sizeX, double sizeY, double sizeZ) { Ptr<Component> component = design->rootComponent(); // Create a sketch Ptr<Sketches> sketches = component->sketches(); Ptr<Sketch> sketch = sketches->add(component->xYConstructionPlane()); Ptr<SketchLines> lines = sketch->sketchCurves()->sketchLines(); Ptr<SketchLineList> recLines = lines->addTwoPointRectangle( Point3D::create(-sizeX / 2, -sizeY / 2, 0), Point3D::create(sizeX / 2, sizeY / 2, 0)); Ptr<Profile> prof = sketch->profiles()->item(0); Ptr<ExtrudeFeatures> extrudes = component->features()->extrudeFeatures(); Ptr<ValueInput> distance = ValueInput::createByReal(sizeZ); Ptr<ExtrudeFeature> ext = extrudes->addSimple(prof, distance, FeatureOperations::NewComponentFeatureOperation); return ext->bodies()->item(0); } // Create a sample sphere Ptr<BRepBody> createSphere(Ptr<Design> design, double radius) { Ptr<Component> component = design->rootComponent(); // Create a new sketch on the xy plane Ptr<Sketches> sketches = component->sketches(); Ptr<ConstructionPlane> xyPlane = component->xYConstructionPlane(); Ptr<Sketch> sketch = sketches->add(component->xYConstructionPlane()); // Draw a circle Ptr<SketchCircles> circles = sketch->sketchCurves()->sketchCircles(); Ptr<SketchCircle> circle = circles->addByCenterRadius(Point3D::create(0, 0, 0), radius); // Draw a line to use as the axis of revolution Ptr<SketchLines> lines = sketch->sketchCurves()->sketchLines(); Ptr<SketchLine> axisLine = lines->addByTwoPoints(Point3D::create(-radius, 0, 0), Point3D::create(radius, 0, 0)); // Get the profile defined by half of the circle Ptr<Profile> prof = sketch->profiles()->item(0); // Create an revolution input to be able to define the input needed for a revolution // while specifying the profile and that a new component is to be created Ptr<RevolveFeatures> revolves = component->features()->revolveFeatures(); Ptr<RevolveFeatureInput> revInput = revolves->createInput(prof, axisLine, FeatureOperations::NewComponentFeatureOperation); // Define that the extent is an angle of 2*pi to get a sphere Ptr<ValueInput> angle = ValueInput::createByReal(2 * PI); revInput->setAngleExtent(false, angle); // Create the extrusion Ptr<RevolveFeature> ext = revolves->add(revInput); return ext->bodies()->item(0); } // Creates a boolean operation between two bodies Ptr<BRepBody> getBodyFromBooleanOperation(Ptr<Design> design, Ptr<BRepBody> body1, Ptr<BRepBody> body2) { Ptr<Component> model = design->activeComponent(); Ptr<Features> features = model->features(); Ptr<ObjectCollection> bodyCollection = ObjectCollection::create(); bodyCollection->add(body2); Ptr<CombineFeatures> combineFeatures = features->combineFeatures(); Ptr<CombineFeatureInput> combineFeatureInput = combineFeatures->createInput(body1, bodyCollection); combineFeatureInput->operation(FeatureOperations(1)); combineFeatureInput->isKeepToolBodies(false); combineFeatureInput->isNewComponent(false); Ptr<CombineFeature> returnValue = combineFeatures->add(combineFeatureInput); Ptr<BRepBody> part = returnValue->bodies()->item(0); return part; }
import math, os, traceback from enum import Enum import adsk.fusion, adsk.core, adsk.cam #################### Script Constants #################### # We assume we are cutting Aluminum here... # Milling tool library to get tools from MILLING_TOOL_LIBRARY = 'Milling Tools (Metric)' # Some material properties for feed and speed calculation ALUMINUM_CUTTING_SPEED = 300 # mm/min ALUMINUM_FEED_PER_TOOTH = 0.1 # mm/tooth # Some tool preset names, known to exist for the selected tools ALUMINUM_PRESET_ROUGHING = 'alu* rou*' ALUMINUM_PRESET_FINISHING = 'Aluminum - Finishing' #################### Useful Enumerators #################### # Some tool types used in this script (enumerator) class ToolType(Enum): BULL_NOSE_END_MILL = 'bull nose end mill' BALL_END_MILL = 'ball end mill' FACE_MILL = 'face mill' # Setup work coordinate system (WCS) location (enumerator) class SetupWCSPoint(Enum): TOP_CENTER = 'top center' TOP_XMIN_YMIN = 'top 1' TOP_XMAX_YMIN = 'top 2' TOP_XMIN_YMAX = 'top 3' TOP_XMAX_YMAX = 'top 4' TOP_SIDE_YMIN = 'top side 1' TOP_SIDE_XMAX = 'top side 2' TOP_SIDE_YMAX = 'top side 3' TOP_SIDE_XMIN = 'top side 4' CENTER = 'center' MIDDLE_XMIN_YMIN = 'middle 1' MIDDLE_XMAX_YMIN = 'middle 2' MIDDLE_XMIN_YMAX = 'middle 3' MIDDLE_XMAX_YMAX = 'middle 4' MIDDLE_SIDE_YMIN = 'middle side 1' MIDDLE_SIDE_XMAX = 'middle side 2' MIDDLE_SIDE_YMAX = 'middle side 3' MIDDLE_SIDE_XMIN = 'middle side 4' BOTTOM_CENTER = 'bottom center' BOTTOM_XMIN_YMIN = 'bottom 1' BOTTOM_XMAX_YMIN = 'bottom 2' BOTTOM_XMIN_YMAX = 'bottom 3' BOTTOM_XMAX_YMAX = 'bottom 4' BOTTOM_SIDE_YMIN = 'bottom side 1' BOTTOM_SIDE_XMAX = 'bottom side 2' BOTTOM_SIDE_YMAX = 'bottom side 3' BOTTOM_SIDE_XMIN = 'bottom side 4' #################### Script Entry Point #################### def run(context) -> None: ui = None try: #################### Initialisation ##################### app = adsk.core.Application.get() ui: adsk.core.UserInterface = app.userInterface # Create a new empty document doc: adsk.core.Document = app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType) # Get the design document used to create the sample part design: adsk.core.Product = app.activeProduct # Switch to manufacturing space camWS: adsk.core.Workspace = ui.workspaces.itemById('CAMEnvironment') camWS.activate() # Get the CAM product products: adsk.core.Products = doc.products #################### Create Sample Part #################### part: adsk.fusion.BRepBody = createSamplePart(design) #################### Select Cutting Tools #################### # Get the tool libraries from the library manager camManager = adsk.cam.CAMManager.get() libraryManager: adsk.cam.CAMLibraryManager = camManager.libraryManager toolLibraries: adsk.cam.ToolLibraries = libraryManager.toolLibraries url: adsk.core.URL = None libUrl = None useHardCodedUrl: bool = False if useHardCodedUrl: # We could use a library URl directly if we know its address ... libUrl = 'systemlibraryroot://Samples/Milling Tools (Metric).json' url = adsk.core.URL.create(libUrl) else: # ... or we can use the tool library objects # Fusion folder in the tool library fusionFolder: adsk.core.URL = toolLibraries.urlByLocation(adsk.cam.LibraryLocations.Fusion360LibraryLocation) fusionLibs: list[str] = getLibrariesURLs(toolLibraries, fusionFolder) # Search the required library url in the libraries for libUrl in fusionLibs: if MILLING_TOOL_LIBRARY in libUrl: url = adsk.core.URL.create(libUrl) break # Load the tool library toolLibrary: adsk.cam.ToolLibrary = toolLibraries.toolLibraryAtURL(url) # Create some variables to host the milling tools which will be used in the operations faceTool: adsk.cam.Tool = None adaptiveTool: adsk.cam.Tool = None finishingTool: adsk.cam.Tool = None # For the roughing operations, search for the face mill and the bull nose using a loop for tool in toolLibrary: # Read the tool type toolType: adsk.cam.ToolType = tool.parameters.itemByName('tool_type').value.value # Select the first face tool found if toolType == ToolType.FACE_MILL.value and not faceTool: faceTool = tool # Search for the roughing tool elif toolType == ToolType.BULL_NOSE_END_MILL.value and not adaptiveTool: # Look for a bull nose end mill tool larger or equal to 12mm but less than 14mm diameter: float = tool.parameters.itemByName('tool_diameter').value.value if diameter >= 1.2 and diameter < 1.4: adaptiveTool = tool # Exit when the 2 tools are found if faceTool and adaptiveTool: break # Using a query, search for a ball end mill tool with diameter between 6 mm and 10 mm with a minimum flute length of 20.001mm finishingTools: list[adsk.cam.Tool] = getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength(toolLibrary, ToolType.BALL_END_MILL.value, 0.6, 1, 2.0001) # For this example, we select the first tool found as our finishing tool finishingTool = finishingTools[0] #################### Create Setup #################### cam = adsk.cam.CAM.cast(products.itemByProductType("CAMProductType")) setups: adsk.cam.Setups = cam.setups setupInput: adsk.cam.SetupInput = setups.createInput(adsk.cam.OperationTypes.MillingOperation) # Create a list for the models to add to the setup Input models:list = [] # Add the part to the model list models.append(part) # Pass the model list to the setup input setupInput.models = models # Create the setup and set some properties setup: adsk.cam.Setup = setups.add(setupInput) setup.name = 'CAM Automation Script Sample' setup.stockMode = adsk.cam.SetupStockModes.RelativeBoxStock # Set the offset mode setup.parameters.itemByName('job_stockOffsetMode').expression = "'simple'" # Set offset stock side setup.parameters.itemByName('job_stockOffsetSides').expression = '0 mm' # Set offset stock top setup.parameters.itemByName('job_stockOffsetTop').expression = '1 mm' # Set setup origin setup.parameters.itemByName('wcs_origin_boxPoint').value.value = SetupWCSPoint.TOP_XMIN_YMIN.value #################### Face Operations #################### # Calculate feed and speed for the face operation toolDiameter: float = faceTool.parameters.itemByName('tool_diameter').value.value # cm numberOfFlutes = faceTool.parameters.itemByName('tool_numberOfFlutes').value.value # int spindleSpeed: float = ALUMINUM_CUTTING_SPEED / math.pi / (toolDiameter * 10) * 1000 # rpm cuttingFeedrate: float = spindleSpeed * ALUMINUM_FEED_PER_TOOTH * numberOfFlutes # mm/min # Create a preset with those calculated feeds facePreset: adsk.cam.ToolPreset = faceTool.presets.add() facePreset.name = 'Aluminum (set by script)' facePreset.parameters.itemByName('tool_spindleSpeed').value.value = int(spindleSpeed) facePreset.parameters.itemByName('tool_feedCutting').expression = str(int(cuttingFeedrate)) + ' mm/min' # Create a face operation input input: adsk.cam.OperationInput = setup.operations.createInput('face') input.tool = faceTool input.toolPreset = facePreset # Assign created preset input.displayName = 'Face Operation' input.parameters.itemByName('tolerance').expression = '0.01 mm' input.parameters.itemByName('stepover').expression = '0.75 * tool_diameter' input.parameters.itemByName('direction').expression = "'climb'" # Determine pass angle along largest part dimension # Get stock box dimensions in cm stockX: float = setup.parameters.itemByName('job_stockInfoDimensionX').value.value stockY: float = setup.parameters.itemByName('job_stockInfoDimensionY').value.value # Determine pass angle to be along largest length (X or Y) of the block if stockX >= stockY: input.parameters.itemByName('passAngle').expression = '0 deg' else: input.parameters.itemByName('passAngle').expression = '90 deg' # Add the operation to the setup faceOp: adsk.cam.OperationBase = setup.operations.add(input) #################### Adaptive Operations #################### input = setup.operations.createInput('adaptive') input.tool = adaptiveTool input.displayName = 'Adaptive Roughing' input.parameters.itemByName('tolerance').expression = '0.1 mm' input.parameters.itemByName('maximumStepdown').expression = '5 mm' input.parameters.itemByName('fineStepdown').expression = '0.25 * maximumStepdown' input.parameters.itemByName('flatAreaMachining').expression = 'false' # Check if there is a tool preset related to aluminum roughing presets: adsk.cam.ToolPresets = adaptiveTool.presets.itemsByName(ALUMINUM_PRESET_ROUGHING) if len(presets) > 0: # Select the first preset found adaptivePreset: adsk.cam.ToolPreset = presets[0] input.toolPreset = adaptivePreset # Add the operation to the setup adaptiveOp: adsk.cam.OperationBase = setup.operations.add(input) #################### Finishing Tool Preset #################### # Select a tool preset from the finishing tool finishingPreset: adsk.cam.ToolPreset = None presets = finishingTool.presets.itemsByName(ALUMINUM_PRESET_FINISHING) if len(presets) > 0: # Use the first aluminum finishing preset found finishingPreset = presets[0] #################### Parallel Operations #################### input = setup.operations.createInput('parallel') input.tool = finishingTool input.displayName = 'Parallel Finishing' input.parameters.itemByName('tolerance').expression = '0.01 mm' input.parameters.itemByName('cuspHeightStepover').expression = '0.005 mm' input.parameters.itemByName('boundaryMode').expression = "'selection'" if finishingPreset: # Assign the finishing tool preset input.toolPreset = finishingPreset # Add the operation to the setup parallelOp: adsk.cam.OperationBase = setup.operations.add(input) # Lets use a contour for the sake of demonstration limitEdge: adsk.fusion.BRepEdge = None for e in part.edges: # This is the inner one: intersection of a plane and a sphere making up a circle if e.geometry.curveType == adsk.core.Curve3DTypes.Circle3DCurveType: limitEdge = e break if limitEdge: # Apply the limiting edge to the operation cadcontours2dParam: adsk.cam.CadContours2dParameterValue = parallelOp.parameters.itemByName('machiningBoundarySel').value chains: adsk.cam.CurveSelections = cadcontours2dParam.getCurveSelections() chain: adsk.cam.ChainSelection = chains.createNewChainSelection() chain.inputGeometry = [limitEdge] cadcontours2dParam.applyCurveSelections(chains) #################### Steep And Shallow Operations #################### # Create folder for finishing operations that require Machining Extension operationInput: adsk.cam.OperationInput = setup.operations.createInput('folder') operationInput.displayName = 'Machining Extension Required' folder: adsk.cam.CAMFolder = setup.operations.add(operationInput) # Create steep and shallow operation in the folder input = setup.operations.createInput('steep_and_shallow') input.tool = finishingTool input.displayName = 'Steep and Shallow Finishing' input.parameters.itemByName('tolerance').expression = '0.01 mm' input.parameters.itemByName('useAvoidFlats').expression = 'true' input.parameters.itemByName('cuspHeightStepdown').expression = '0.005 mm' input.parameters.itemByName('cuspHeightStepover').expression = 'cuspHeightStepdown' input.parameters.itemByName('spiral').expression = 'true' input.parameters.itemByName('shallowSpiral').expression = 'true' input.parameters.itemByName('offsetSmoothing').expression = 'true' # Assign the finishing tool preset if finishingPreset: input.toolPreset = finishingPreset # Add the operation to the folder steepAndShallowOp: adsk.cam.OperationBase = folder.operations.add(input) # Check if this toolpath is generatable as "steep_and_shallow" requires the manufacturing extension isSteepAndShallowGeneratable: bool = False for op in setup.operations.compatibleStrategies: if op.name == 'steep_and_shallow': if op.isGenerationAllowed: # If isGenerationAllowed is False, the extension isn't active, preventing generation of the steep_and_shallow operation isSteepAndShallowGeneratable = True break #################### Generate Operations #################### # Include the valid operations to generate operations = adsk.core.ObjectCollection.create() operations.add(faceOp) operations.add(adaptiveOp) operations.add(parallelOp) if isSteepAndShallowGeneratable: operations.add(steepAndShallowOp) # create progress bar progressDialog: adsk.core.ProgressDialog = ui.createProgressDialog() progressDialog.isCancelButtonShown = False progressDialog.show('Generating operations...', '%p%', 0, 100) adsk.doEvents() # Allow Fusion to update so the progressDialog shows up nicely # Generate the valid operations gtf: adsk.cam.GenerateToolpathFuture = cam.generateToolpath(operations) # Wait for the generation to be finished and update progress bar while not gtf.isGenerationCompleted: # Calculate progress and update progress bar total = gtf.numberOfOperations completed = gtf.numberOfCompleted progress = int(completed * 100 / total) progressDialog.progressValue = progress adsk.doEvents() # Allow Fusion to update so the screen doesn't freeze # Generation done progressDialog.progressValue = 100 progressDialog.hide() #################### NC Program And Post-processing #################### # Get the post library from library manager postLibrary: adsk.cam.PostLibrary = libraryManager.postLibrary # Query post library to get postprocessor list postQuery: adsk.cam.PostConfigurationQuery = postLibrary.createQuery(adsk.cam.LibraryLocations.Fusion360LibraryLocation) postQuery.vendor = "Autodesk" postQuery.capability = adsk.cam.PostCapabilities.Milling postConfigs: list[adsk.cam.PostConfiguration] = postQuery.execute() # Find the "XYZ" post in the post library and import it to local library for config in postConfigs: if config.description == 'XYZ': url = adsk.core.URL.create("user://") importedURL: adsk.core.URL = postLibrary.importPostConfiguration(config, url, "NCProgramSamplePost.cps") # Get the imported local post config postConfig: adsk.cam.PostConfiguration = postLibrary.postConfigurationAtURL(importedURL) # Create NCProgramInput object ncInput: adsk.cam.NCProgramInput = cam.ncPrograms.createInput() ncInput.displayName = 'NC Program Sample' # Change some nc program parameters... ncParameters: adsk.cam.CAMParameters = ncInput.parameters ncParameters.itemByName('nc_program_filename').value.value = 'NCProgramSample' ncParameters.itemByName('nc_program_openInEditor').value.value = True # Set user desktop as output directory (Windows and Mac) # Make the path valid for Fusion by replacing \\ to / in the path desktopDirectory = os.path.expanduser("~/Desktop").replace('\\', '/') ncParameters.itemByName('nc_program_output_folder').value.value = desktopDirectory # Select the operations to generate (we skip steep_and_shallow here) ncInput.operations = [faceOp, adaptiveOp, parallelOp] # Add a new ncprogram from the ncprogram input newProgram: adsk.cam.NCProgram = cam.ncPrograms.add(ncInput) # Set post processor newProgram.postConfiguration = postConfig # Change some post parameter postParameters: adsk.cam.CAMParameters = newProgram.postParameters # NcProgram parameters are passed as theyare to the postprocessor (they have no units) postParameters.itemByName('builtin_tolerance').value.value = 0.01 postParameters.itemByName('builtin_minimumChordLength').value.value = 0.33 # Update/apply post parameters newProgram.updatePostParameters(postParameters) # Set post options, by default post process only valid operations containing toolpath data postOptions = adsk.cam.NCProgramPostProcessOptions.create() # Post-process newProgram.postProcess(postOptions) except: if ui: ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) #################### Functions To Make Our Life Easier #################### def getLibrariesURLs(libraries: adsk.cam.ToolLibraries, url: adsk.core.URL) -> list[str]: ''' Return the list of libraries' URLs for the specified libraries ''' urls: list[str] = [] libs: list[adsk.core.URL] = libraries.childAssetURLs(url) for lib in libs: urls.append(lib.toString()) for folder in libraries.childFolderURLs(url): urls = urls + getLibrariesURLs(libraries, folder) return urls def getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength(toolLibrary: adsk.cam.ToolLibrary, tooltype: str, minDiameter: float, maxDiameter: float, minimumFluteLength: float = None) -> list[adsk.cam.Tool]: ''' Return a list of tools that satisfy the search ''' query: adsk.cam.ToolQuery = toolLibrary.createQuery() # Set the search critera query.criteria.add('tool_type', adsk.core.ValueInput.createByString(tooltype)) query.criteria.add('tool_diameter.min', adsk.core.ValueInput.createByReal(minDiameter)) query.criteria.add('tool_diameter.max', adsk.core.ValueInput.createByReal(maxDiameter)) if minimumFluteLength: query.criteria.add('tool_fluteLength.min', adsk.core.ValueInput.createByReal(minimumFluteLength)) # Get query results results: list[adsk.cam.ToolQueryResult] = query.execute() # Get the tools from the query tools: list[adsk.cam.Tool] = [] for result in results: # Results have a tool, url, toollibrary and the index of the tool in that library: we just return the tool here tools.append(result.tool) return tools #################### CAD Creation #################### def createSamplePart(design: adsk.fusion.Design) -> adsk.fusion.BRepBody: """ Creates the sample part for this script """ # The sample part is a box containing a concave side generated by subtracting the intersection from a sphere. # The lower face of the box is at Z=0 and we position the sphere above but intersecting with the box upper face. # Fusion's default behaviour is now to ground the first component of an assembly to the parent. # This can be overriden, see Preferences ->General ->Design ->Assemblies ->First component grounded to parent # However if we make the first component the box, we can move the sphere without changing anything box: adsk.fusion.BRepBody = createBox(design, 22, 15, 5) # 1st component occurrence sphere: adsk.fusion.BRepBody = createSphere(design, 7.5) # 2nd component occurrence # Get the root component rootComp: adsk.fusion.Component = design.rootComponent occs: adsk.fusion.Occurrences = rootComp.occurrences # Get the second Occurrence (sphere) as the first occurrence (box) may be grounded to the parent occ: adsk.fusion.Occurrence = occs.item(1) mat: adsk.core.Matrix3D = occ.transform # Matrix translation, moving the sphere up by 10 units along Z axis mat.translation = adsk.core.Vector3D.create(0, 0, 10) # Set transform occ.transform = mat # Snapshot - Determining the position is important!!! design.snapshots.add() # Cut the sphere/box intersection from the box to leave a concave face part: adsk.fusion.BRepBody = getBodyFromBooleanOperation(design, box, sphere) return part def createBox(design: adsk.fusion.Design, sizeX: float, sizeY: float, sizeZ: float) -> adsk.fusion.BRepBody: ''' Creates a sample box''' component: adsk.fusion.Component = design.rootComponent # Create sketch sketches: adsk.fusion.Sketches = component.sketches sketch: adsk.fusion.Sketch = sketches.add(component.xYConstructionPlane) lines: adsk.fusion.SketchLines = sketch.sketchCurves.sketchLines lines.addTwoPointRectangle(adsk.core.Point3D.create(-sizeX / 2, -sizeY / 2, 0), adsk.core.Point3D.create(sizeX / 2, sizeY / 2, 0)) prof: adsk.fusion.Profile = sketch.profiles.item(0) extrudes: adsk.fusion.ExtrudeFeatures = component.features.extrudeFeatures distance = adsk.core.ValueInput.createByReal(sizeZ) ext: adsk.fusion.ExtrudeFeature = extrudes.addSimple(prof, distance, adsk.fusion.FeatureOperations.NewComponentFeatureOperation) return ext.bodies.item(0) def createSphere(design: adsk.fusion.Design, radius: float) -> adsk.fusion.BRepBody: ''' Creates a sample sphere ''' component: adsk.fusion.Component = design.rootComponent # Create a new sketch on the xy plane. sketches: adsk.fusion.Sketches = component.sketches xyPlane: adsk.fusion.ConstructionPlane = component.xYConstructionPlane sketch: adsk.fusion.Sketch = sketches.add(xyPlane) # Draw a circle. circles: adsk.fusion.SketchCircles = sketch.sketchCurves.sketchCircles circles.addByCenterRadius(adsk.core.Point3D.create(0, 0, 0), radius) # Draw a line to use as the axis of revolution. lines: adsk.fusion.SketchLines = sketch.sketchCurves.sketchLines axisLine: adsk.fusion.SketchLine = lines.addByTwoPoints(adsk.core.Point3D.create(-radius, 0, 0), adsk.core.Point3D.create(radius, 0, 0)) # Get the profile defined by half of the circle. prof: adsk.fusion.Profile = sketch.profiles.item(0) # Create an revolution input for a revolution while specifying the profile and that a new component is to be created revolves: adsk.fusion.RevolveFeatures = component.features.revolveFeatures revInput: adsk.fusion.RevolveFeatureInput = revolves.createInput(prof, axisLine, adsk.fusion.FeatureOperations.NewComponentFeatureOperation) # Define that the extent is an angle of 2*pi to get a sphere angle = adsk.core.ValueInput.createByReal(2*math.pi) revInput.setAngleExtent(False, angle) # Create the extrusion. ext: adsk.fusion.RevolveFeature = revolves.add(revInput) return ext.bodies.item(0) def getBodyFromBooleanOperation(design: adsk.fusion.Design, body1: adsk.fusion.BRepBody, body2: adsk.fusion.BRepBody) -> adsk.fusion.BRepBody: """ Creates a boolean operation between two bodies """ model: adsk.fusion.Component = design.activeComponent features: adsk.fusion.Features = model.features # Create a collection and add our sphere bodyCollection = adsk.core.ObjectCollection.create() bodyCollection.add(body2) # Create a CombineFeatureInput using our box (body1), sphere (bodyCollection) specifying a 'cut' operation combineFeatures: adsk.fusion.CombineFeatures = features.combineFeatures combineFeatureInput: adsk.fusion.CombineFeatureInput = combineFeatures.createInput(body1, bodyCollection) combineFeatureInput.operation = adsk.fusion.FeatureOperations.CutFeatureOperation combineFeatureInput.isKeepToolBodies = False combineFeatureInput.isNewComponent = False # Generate our combined feature returnValue: adsk.fusion.CombineFeature = combineFeatures.add(combineFeatureInput) part: adsk.fusion.BRepBody = returnValue.bodies[0] return part