This sample script demonstrates three different methods for feature recognition: one for holes and two for pockets.
The script starts by creating a simple component which is then used to demonstrate the three methods. After the features are recognised they are coloured and milling and drilling operations are created for each feature.
RecognizedHoleGroup returns a list of BRepFaces that can be used as selections for the drilling operation. RecognizedPocket and PocketRecognitionSelection do not return BRepFaces, and their output needs additional processing before the output can be used for creating machining operations.
The sample script demonstrates a couple of different methods, including finding the pocket BRepFaces and creating sketches from the recognized pockets.
This script works only if the Manufacturing Extension is active.
import adsk.core, adsk.fusion, adsk.cam, traceback import math from enum import Enum #################### Some constants and enumerators used in the script #################### # Milling & drilling tool libraries to get tools from MILLING_TOOL_LIBRARY_URL = adsk.core.URL.create('systemlibraryroot://Samples/Milling Tools (Metric).json') DRILLING_TOOL_LIBRARY_URL = adsk.core.URL.create('systemlibraryroot://Samples/Hole Making Tools (Metric).json') # Colors POCKET_WALL_FACES_COLOR = adsk.core.Color.create(0, 255, 255, 255) POCKET_BOTTOM_FACES_COLOR = adsk.core.Color.create(0, 145, 230, 255) HOLE_SIMPLE_COLOR = adsk.core.Color.create(130, 225, 10, 255) HOLE_COUNTERBORE_COLOR = adsk.core.Color.create(180, 120, 255, 255) # BRep Search PROXIMITY_TOLERANCE = -1 # define BRep Search proximity tolerance # Some tool types used in this script (enumerator) class ToolType(Enum): BULL_NOSE_END_MILL = 'bull nose end mill' DRILL = 'drill' FLAT_END_MILL = 'flat end mill' SPOT_DRILL = 'spot drill' # Some variables _app = adsk.core.Application.get() _ui = _app.userInterface #################### ENTRY POINT ##################### def run(context): try: # create a new empty document doc = _app.documents.add(adsk.core.DocumentTypes.FusionDesignDocumentType) # switch to manufacturing space camWS = _ui.workspaces.itemById('CAMEnvironment') camWS.activate() # get the CAM product products = doc.products cam: adsk.cam.CAM = products.itemByProductType("CAMProductType") # get the CAD product design: adsk.fusion.Design = products.itemByProductType('DesignProductType') # Get the root component of the active design as we will need to create sketches later on... rootComponent = design.rootComponent sketches = rootComponent.sketches #################### CREATE SAMPLE PART #################### body = createSampleBody(rootComponent) #################### TOOL LIBRARIES #################### # get the tool libraries from the library manager camManager = adsk.cam.CAMManager.get() libraryManager = camManager.libraryManager toolLibraries = libraryManager.toolLibraries # load tool libraries millingToolLibrary = toolLibraries.toolLibraryAtURL(MILLING_TOOL_LIBRARY_URL) drillingToolLibrary = toolLibraries.toolLibraryAtURL(DRILLING_TOOL_LIBRARY_URL) ####################### HOLE & POCKETS RECOGNITION - SAMPLE SCRIPT MAIN LOGIC STARTS ####################### # create setups holeRecognitionSetup = createSetup('Holes (using "RecognizedHoleGroup")', body) pocketRecognitionSetup = createSetup('Pockets (using "RecognizedPocket")', body) pocketSelectionSetup = createSetup('Pockets (using "PocketRecognitionSelection")', body) # get body parent component pocketComponent = body.parentComponent # get a tool to machine the pockets tools = getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength(millingToolLibrary, ToolType.FLAT_END_MILL.value, 1, 1, 2) roughingTool = tools[0] # use the first tool found ########## MAKE POCKETS USING POCKET RECOGNITION API ########## # pocket search vector: only look for pockets from top view: visible and machinable from Z+ pocketSearchVector = adsk.core.Vector3D.create(0, 0, -1) # recognize pockets pockets = adsk.cam.RecognizedPocket.recognizePockets(body, pocketSearchVector) # get boundary key points to find bottom faces (if any) and create roughing operation for pocket in pockets: # check if the pocket is circular (if it's basically a simple hole) since we will deal with those using drilling if isCircularPocket(pocket): continue # pocket boundaries boundaries = pocket.boundaries # get pocket boundary key points (used later on to find and color faces) points: list[adsk.core.Point3D] = [] for boundary in boundaries: boundaryPoints = getBoundaryKeyPoints(boundary) points.extend(boundaryPoints) # create pocket operations if pocket.isThrough: # THROUGH POCKETS: searching side faces for coloring. Bottom faces are missing so we will draw sketch to define pocket bottom boundary. # search the walls face breps using the points of the boundary pocketWallFaces = getPocketWallFaces(pocketComponent, points) # color pocket walls colorFaces(design, pocketWallFaces, 'pocketWallFacesColor', POCKET_WALL_FACES_COLOR) # Create a sketch on XY plane since there are no bottom face to use here sketch = sketches.add(rootComponent.xYConstructionPlane) sketch.name = 'Through pocket sketch' drawSketchCurves(sketch, boundary) # create operation using that sketch op = createClosedThroughPocketOperation(pocketRecognitionSetup, 'Closed through pocket', sketch, pocket, roughingTool) else: # BLIND POCKETS: searching the pocket bottom face to use in the operation and side faces for coloring. # search the bottom face breps using the points of the boundary pocketBottomFaces = getPocketBottomFaces(pocketComponent, points) # search the walls face breps using the points of the boundary pocketWallFaces = getPocketWallFaces(pocketComponent, points) # color pocket bottom faces colorFaces(design, pocketBottomFaces, 'pocketBottomFaces', POCKET_BOTTOM_FACES_COLOR) # color pocket walls colorFaces(design, pocketWallFaces, 'pocketWallFacesColor', POCKET_WALL_FACES_COLOR) # define pocket name if pocket.isClosed: name = 'Closed blind pocket' else: name = 'Open blind pocket' op = createBlindPocketOperation(pocketRecognitionSetup, name, pocketBottomFaces, pocket, roughingTool) # Give control back to Fusion so it can update the graphics. adsk.doEvents() ########## MAKE POCKETS USING POCKET RECOGNITION SELECTION (FROM UI) ########## # create basic roughing operations # the distinction between the pockets is done by filtering the pocket heights, within the functions below... op = createClosedThroughPocketSelectionOperation(pocketSelectionSetup, 'Closed through pocket (PocketSelection)', roughingTool) op = createClosedBlindPocketSelectionOperation(pocketSelectionSetup, 'Closed blind pocket (PocketSelection)', roughingTool) ########## MAKE HOLES USING HOLE GROUP RECOGNITION API ########## # hole groups recognition using "adsk.cam.RecognizedHoleGroup()" is grouping holes of the same type # you could also use "adsk.cam.RecognizedHole()": this will give you all holes but ungrouped... recognizedHolesInput = adsk.cam.RecognizedHolesInput.create() holeGroups = adsk.cam.RecognizedHoleGroup.recognizeHoleGroupsWithInput([body], recognizedHolesInput) # loop through the hole groups found for holeGroup in holeGroups: # analyse a hole from the group to understand their geometry (hole from a group are all the same) holeToCheck = holeGroup.item(0) # check the number of segment and the geometry that make the hole to identify the hole type if holeToCheck.segmentCount == 1: firstSegment = holeToCheck.segment(0) if firstSegment.holeSegmentType == adsk.cam.HoleSegmentType.HoleSegmentTypeCylinder: # this is a simple hole made of one cylinder so let's drill that hole group # color faces for hole in holeGroup: simpleHoleFaces :list[adsk.fusion.BRepFace] = [] simpleHoleFaces.extend(hole.segment(0).faces) colorFaces(design, simpleHoleFaces, 'simpleHoleColor', HOLE_SIMPLE_COLOR) # tool drillDiameter = firstSegment.bottomDiameter # check hole diameter to select the right drill drillDepth = firstSegment.height + 0.5 # check the hole length to make sure the drill is long enough... and adding a bit of clearance of 5mm drillTools = getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength(drillingToolLibrary, ToolType.DRILL.value, drillDiameter, drillDiameter, drillDepth) drillTool = drillTools[0] # pick first tool found # operation createSimpleDrillOperation('Simple drill', holeRecognitionSetup, drillTool, holeGroup) elif holeToCheck.segmentCount == 4: # a hole with 4 segments might be a counterbore through model with a top chamfer... so let's check the geometry... firstSegment = holeToCheck.segment(0) secondSegment = holeToCheck.segment(1) thirdSegment = holeToCheck.segment(2) fourthSegment = holeToCheck.segment(3) if firstSegment.holeSegmentType == adsk.cam.HoleSegmentType.HoleSegmentTypeCone: if secondSegment.holeSegmentType == adsk.cam.HoleSegmentType.HoleSegmentTypeCylinder: if thirdSegment.holeSegmentType == adsk.cam.HoleSegmentType.HoleSegmentTypeFlat: if fourthSegment.holeSegmentType == adsk.cam.HoleSegmentType.HoleSegmentTypeCylinder: # a hole made by a cone, a cylinder, a flat and finally a cylinder is our definition of a coulterbore through model with a top chamfer, in this example... # we will ignore other types of holes made by 4 segments here if any... # color faces for hole in holeGroup: couterboreHoleFaces :list[adsk.fusion.BRepFace] = [] couterboreHoleFaces.extend(hole.segment(0).faces) couterboreHoleFaces.extend(hole.segment(1).faces) couterboreHoleFaces.extend(hole.segment(2).faces) couterboreHoleFaces.extend(hole.segment(3).faces) colorFaces(design, couterboreHoleFaces, 'counterboreHoleColor', HOLE_COUNTERBORE_COLOR) # pick tool drillDiameter = fourthSegment.bottomDiameter # check hole diameter to select the right drill # check the hole length to make sure the drill is long enough... and adding a bit of clearance of 5mm drillDepth = firstSegment.height + secondSegment.height + fourthSegment.height + 0.5 # no need to using the third segment height since it's a flat face drillTools = getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength(drillingToolLibrary, ToolType.DRILL.value, drillDiameter, drillDiameter, drillDepth) drillTool = drillTools[0] # pick first tool found # drill createCounterboreDrillOperation('Counterbore drill', holeRecognitionSetup, drillTool, holeGroup) # check counterbopre diameter to select the right flat end mill # we will drill the hole first meaning we don't need to take this into account... # pick a flat end mill for the counterbore minCounterboreToolDiameter = secondSegment.bottomDiameter - fourthSegment.bottomDiameter maxCounterboreToolDiameter = secondSegment.bottomDiameter * 0.75 counterboreToolMinFluteLength = firstSegment.height + secondSegment.height + 0.5 # adding 5mm clearance counterboreTools = getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength(millingToolLibrary, ToolType.FLAT_END_MILL.value, minCounterboreToolDiameter, maxCounterboreToolDiameter, counterboreToolMinFluteLength) counterboreTool = counterboreTools[0] # pick first tool found # counterbore createCounterboreMillOperation('Counterbore mill', holeRecognitionSetup, counterboreTool, holeGroup) # select tools # pick a spot drill tool for the top chamfer (we will roll over the chamfer so no need for a very large tool) minChamferToolDiameter = 1 maxChamferToolDiameter = 1.4 chamferTools = getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength(drillingToolLibrary, ToolType.SPOT_DRILL.value, minChamferToolDiameter, maxChamferToolDiameter) chamferTool = chamferTools[0] # pick first tool found # chamfer createCounterboreChamferOperation('Counterbore chamfer', holeRecognitionSetup, chamferTool, holeGroup) # Give control back to Fusion so it can update the graphics. adsk.doEvents() # compute all cam.generateAllToolpaths(True) except: if _ui: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) ####################### SAMPLE SCRIPT MAIN LOGIC ENDS ####################### ############################################################################# ####################### TOOLS ####################### def getToolsFromLibraryByTypeDiameterRangeAndMinFluteLength(toolLibrary: adsk.cam.ToolLibrary, tooltype: str, minDiameter: float, maxDiameter: float, minimumFluteLength: float = None): ''' Return a list of tools that fits the search ''' # set the search critera query = toolLibrary.createQuery() 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 = query.execute() # get the tools from the query tools: list[adsk.cam.Tool] = [] for result in results: # a result has 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 ####################### SETUPS ####################### def createSetup(name: str, body: adsk.fusion.BRepBody): ''' Create a setup ''' app = adsk.core.Application.get() doc = app.activeDocument products = doc.products cam = adsk.cam.CAM.cast(products.itemByProductType("CAMProductType")) setups = cam.setups # create setup input and set parameters input = setups.createInput(adsk.cam.OperationTypes.MillingOperation) input.models = [body] input.name = name input.stockMode = adsk.cam.SetupStockModes.RelativeBoxStock input.parameters.itemByName('job_stockOffsetMode').expression = "'keep'" # create the setup setup = setups.add(input) return setup ####################### POCKETS (USING API) ####################### def createClosedThroughPocketOperation(setup: adsk.cam.Setup, name: str, sketch: adsk.fusion.Sketch, pocket: adsk.cam.RecognizedPocket, tool: adsk.cam.Tool) -> adsk.cam.Operation: ''' Produce the toolpath for the closed through pocket using API ''' input = setup.operations.createInput('adaptive2d') input.displayName = name input.tool = tool input.parameters.itemByName('doMultipleDepths').expression = 'true' pocketHeightIncludingBottomOffsetInMM = round((pocket.depth * 10) + 2, 3) # convert cm to mm and add 2 mm from bottomHeight_offset input.parameters.itemByName('maximumStepdown').expression = str(pocketHeightIncludingBottomOffsetInMM / 2) + ' mm' # divide total height by 2 to get 2 passes input.parameters.itemByName('topHeight_mode').expression = "'from contour'" input.parameters.itemByName('topHeight_offset').expression = str(pocket.depth * 10) + ' mm' input.parameters.itemByName('bottomHeight_offset').expression = str(-2) + 'mm' # set bottom to be 2 mm below pocket bottom # apply the shetch boundary to the operation input pocketSelection: adsk.cam.CadContours2dParameterValue = input.parameters.itemByName('pockets').value chains = pocketSelection.getCurveSelections() chain = chains.createNewSketchSelection() chain.inputGeometry = [sketch] chain.loopType = adsk.cam.LoopTypes.OnlyOutsideLoops chain.sideType = adsk.cam.SideTypes.AlwaysInsideSideType pocketSelection.applyCurveSelections(chains) # add to the setup op = setup.operations.add(input) return op def createBlindPocketOperation(setup: adsk.cam.Setup, name: str, pocketBottomFaces: list[adsk.fusion.BRepFace], pocket: adsk.cam.RecognizedPocket, tool: adsk.cam.Tool) -> adsk.cam.Operation: ''' Produce the toolpath for the closed blind pocket using API ''' input = setup.operations.createInput('adaptive2d') input.displayName = name input.tool = tool input.parameters.itemByName('doMultipleDepths').expression = 'true' input.parameters.itemByName('maximumStepdown').expression = str(round(pocket.depth / 2, 3) * 10) + ' mm' # divide total height by 2 to get 2 passes input.parameters.itemByName('topHeight_mode').expression = "'from contour'" input.parameters.itemByName('topHeight_offset').expression = str(pocket.depth * 10) + ' mm' # add to the setup op = setup.operations.add(input) # apply the limits edge to the operation pocketSelection: adsk.cam.CadContours2dParameterValue = op.parameters.itemByName('pockets').value chains = pocketSelection.getCurveSelections() chain = chains.createNewPocketSelection() chain.inputGeometry = pocketBottomFaces pocketSelection.applyCurveSelections(chains) return op ####################### POCKETS (USING UI) ####################### def createClosedThroughPocketSelectionOperation(setup: adsk.cam.Setup, name: str, tool: adsk.cam.Tool): ''' Produce the toolpath for the closed through pocket using UI ''' input = setup.operations.createInput('adaptive2d') input.displayName = name input.tool = tool input.parameters.itemByName('doMultipleDepths').expression = 'true' input.parameters.itemByName('maximumStepdown').expression = '12 mm' # just dividiung the pocket height by 2 to have 2 steps input.parameters.itemByName('bottomHeight_offset').expression = str(-2) + ' mm' # bottom height = 2 mm below pocket bottom # add to the setup op = setup.operations.add(input) # apply the shetch boundary to the operation pocketSelection: adsk.cam.CadContours2dParameterValue = op.parameters.itemByName('pockets').value chains = pocketSelection.getCurveSelections() chain = chains.createNewPocketRecognitionSelection() chain.maximumPocketDepth = 2.5 # (cm) define some pocket recognition settings to filter pockets by height (measured from UI) chain.minimumPocketDepth = 1.5 # (cm) pocketSelection.applyCurveSelections(chains) return op def createClosedBlindPocketSelectionOperation(setup: adsk.cam.Setup, name: str, tool: adsk.cam.Tool): ''' Produce the toolpath for the closed blind pocket using UI ''' input = setup.operations.createInput('adaptive2d') input.displayName = name input.tool = tool input.parameters.itemByName('doMultipleDepths').expression = 'true' input.parameters.itemByName('maximumStepdown').expression = '6 mm' # add to the setup op = setup.operations.add(input) # apply the shetch boundary to the operation pocketSelection: adsk.cam.CadContours2dParameterValue = op.parameters.itemByName('pockets').value chains = pocketSelection.getCurveSelections() chain = chains.createNewPocketRecognitionSelection() chain.maximumPocketDepth = 1.5 # (cm) define some pocket recognition settings to filter pockets by height (measured from UI) chain.minimumPocketDepth = 0 # (cm) pocketSelection.applyCurveSelections(chains) return op ####################### POCKET HELPER FUNCTION ####################### def isCircularPocket(pocket: adsk.cam.RecognizedPocket) -> bool: ''' Returns true if this is a circular pocket (= a hole) made of boundaries with circular segments only ''' isCircleFound = False boundaries = pocket.boundaries for i in range(len(boundaries)): boundary = boundaries[i] for j in range(boundary.count): segment = boundary.item(j) if segment.classType() == adsk.core.Circle3D.classType(): isCircleFound = True else: return False return isCircleFound def getBoundaryKeyPoints(boundary: list[adsk.core.Curve3DPath]) -> list[adsk.core.Point3D]: ''' Get some key points on the pocket boundary ''' points: list[adsk.core.Point3D] = [] for segment in boundary: # cast to get the actual segment type (casting returns None if the wrong object is passed) line3D = adsk.core.Line3D.cast(segment) arc3D = adsk.core.Arc3D.cast(segment) if line3D: points.append(line3D.startPoint) points.append(line3D.endPoint) elif arc3D: points.append(arc3D.startPoint) points.append(arc3D.endPoint) else: classType = segment.classType() raise Exception('Unsupported pocket curve type for this sample script: ' + classType) return points def getPocketBottomFaces(component: adsk.fusion.Component, points: list[adsk.core.Point3D]) -> list[adsk.fusion.BRepFace]: ''' Search the pocket bottom faces using the provided points ''' # search the bottom face breps using the provided points of the boundary pocketBottomFaces: list[adsk.fusion.BRepFace] = [] # define the Z value of the pocket bottom using a recognized boundary point pocketBottomZ = points[0].z for point in points: breps = component.findBRepUsingPoint(point, adsk.fusion.BRepEntityTypes.BRepFaceEntityType, PROXIMITY_TOLERANCE, True) for brep in breps: # cast so we have a nice typed variable brep = adsk.fusion.BRepFace.cast(brep) # filter to only find the flat faces (not the pocket walls) if brep.boundingBox.minPoint.z == pocketBottomZ and brep.boundingBox.maxPoint.z == pocketBottomZ: if not brep in pocketBottomFaces: pocketBottomFaces.append(brep) return pocketBottomFaces def getPocketWallFaces(component: adsk.fusion.Component, points: list[adsk.core.Point3D]) -> list[adsk.fusion.BRepFace]: ''' Search the pocket wall faces using the provided points ''' # search the walls face breps using the points of the boundary pocketWallFaces: list[adsk.fusion.BRepFace] = [] # define the Z value of the pocket bottom using a recognized boundary point pocketBottomZ = points[0].z for point in points: breps = component.findBRepUsingPoint(point, adsk.fusion.BRepEntityTypes.BRepFaceEntityType, PROXIMITY_TOLERANCE, True) for brep in breps: # cast so we have a nice typed variable brep = adsk.fusion.BRepFace.cast(brep) # filter to only find the wall faces (not the pocket bottom faces) if brep.boundingBox.minPoint.z == pocketBottomZ and brep.boundingBox.maxPoint.z > pocketBottomZ: if not brep in pocketWallFaces: pocketWallFaces.append(brep) return pocketWallFaces ####################### DRILLING ####################### def createSimpleDrillOperation(name: str, setup: adsk.cam.Setup, tool: adsk.cam.Tool, holeGroup: adsk.cam.RecognizedHoleGroup): ''' Create simple drilling operation ''' input = setup.operations.createInput('drill') input.displayName = name input.tool = tool input.parameters.itemByName('drillTipThroughBottom').expression = 'true' input.parameters.itemByName('breakThroughDepth').expression = '2 mm' # select the holes faces to drill faces: list[adsk.fusion.BRepFace] = [] for i in range(holeGroup.count): hole = holeGroup.item(i) firstSegment = hole.segment(0) faces.extend(firstSegment.faces) holeSelection: adsk.cam.CadObjectParameterValue = input.parameters.itemByName('holeFaces').value holeSelection.value = faces # add to setup setup.operations.add(input) def createCounterboreDrillOperation(name: str, setup: adsk.cam.Setup, tool: adsk.cam.Tool, holeGroup: adsk.cam.RecognizedHoleGroup): ''' Create drilling operation for the counterbore hole ''' input = setup.operations.createInput('drill') input.displayName = name input.tool = tool input.parameters.itemByName('drillTipThroughBottom').expression = 'true' input.parameters.itemByName('breakThroughDepth').expression = '2 mm' # select the holes faces to drill faces: list[adsk.fusion.BRepFace] = [] for i in range(holeGroup.count): hole = holeGroup.item(i) firstSegment = hole.segment(0) secondSegment = hole.segment(1) fourthSegment = hole.segment(3) faces.extend(firstSegment.faces) faces.extend(secondSegment.faces) faces.extend(fourthSegment.faces) holeSelection: adsk.cam.CadObjectParameterValue = input.parameters.itemByName('holeFaces').value holeSelection.value = faces # add to setup setup.operations.add(input) def createCounterboreMillOperation(name: str, setup: adsk.cam.Setup, tool: adsk.cam.Tool, holeGroup: adsk.cam.RecognizedHoleGroup): ''' Create milling operation for the counterbore part of the hole ''' input = setup.operations.createInput('contour2d') input.displayName = name input.tool = tool input.parameters.itemByName('doLeadIn').expression = 'false' input.parameters.itemByName('doRamp').expression = 'true' input.parameters.itemByName('rampAngle').expression = '2 deg' input.parameters.itemByName('exit_verticalRadius').expression = '0 mm' input.parameters.itemByName('exit_radius').expression = '0 mm' # select the counterbore bottom edge to mill edges: list[adsk.fusion.BRepEdge] = [] for i in range(holeGroup.count): hole = holeGroup.item(i) secondSegment = hole.segment(1) # counterbore segment edge = getHoleSegmentBottomEdge(secondSegment) edges.append(edge) holeSelection: adsk.cam.CadContours2dParameterValue = input.parameters.itemByName('contours').value chains = holeSelection.getCurveSelections() # each edge is a separate chain selection since it's own by separate holes for edge in edges: chain = chains.createNewChainSelection() chain.isReverted = True chain.inputGeometry = [edge] holeSelection.applyCurveSelections(chains) # add to setup setup.operations.add(input) def createCounterboreChamferOperation(name: str, setup: adsk.cam.Setup, tool: adsk.cam.Tool, holeGroup: adsk.cam.RecognizedHoleGroup): ''' Create an operation for the top chamfer of the counterbore ''' input = setup.operations.createInput('chamfer2d') input.displayName = name input.tool = tool input.parameters.itemByName('chamferClearance').expression = '0 mm' input.parameters.itemByName('entry_distance').expression = '5 mm' input.parameters.itemByName('chamferTipOffset').expression = '1 mm' # select the counterbore chamfers bottom edge to mill edges: list[adsk.fusion.BRepEdge] = [] for i in range(holeGroup.count): hole = holeGroup.item(i) firstSegment = hole.segment(0) # chamfer segment edge = getHoleSegmentBottomEdge(firstSegment) edges.append(edge) holeSelection: adsk.cam.CadContours2dParameterValue = input.parameters.itemByName('contours').value chains = holeSelection.getCurveSelections() for edge in edges: # each edge is a separate chain selection since it's own by separate holes chain = chains.createNewChainSelection() chain.isReverted = True chain.inputGeometry = [edge] holeSelection.applyCurveSelections(chains) # add to setup setup.operations.add(input) def getHoleSegmentBottomEdge(segment: adsk.cam.RecognizedHoleSegment) -> adsk.fusion.BRepEdge: ''' Get the bottom edge of a given hole segment (assuming the hole is algned on Z+) ''' # we assume: # - the segment is made by one face # - the hole is aligned with Z+ (bounding box checking) if len(segment.faces) != 1: raise Exception('A hole segment with a single face is expected!') face = adsk.fusion.BRepFace.cast(segment.faces[0]) faceEdges = face.edges if len(faceEdges) != 2: raise Exception('A hole segment with a single face made of 2 edges is expected!') if faceEdges[0].boundingBox.maxPoint.z < faceEdges[1].boundingBox.maxPoint.z: edge = faceEdges[0] else: edge = faceEdges[1] return edge ####################### SKETCHING ####################### def drawSketchCurves(sketch: adsk.fusion.Sketch, boundary: list[adsk.core.Curve3D]): ''' Create a sketch from given pocket boundary or island ''' for segment in boundary: # cast to get the actual segment type (casting returns None if the wrong object is passed) line3D = adsk.core.Line3D.cast(segment) arc3D = adsk.core.Arc3D.cast(segment) circle3D = adsk.core.Circle3D.cast(segment) if line3D: startPoint = line3D.startPoint endPoint = line3D.endPoint sketchLine(sketch, startPoint, endPoint) elif arc3D: startPoint = arc3D.startPoint centerPoint = arc3D.center sweepAngle = arc3D.endAngle normal = arc3D.normal sketchTwoPointArc(sketch, centerPoint, startPoint, sweepAngle, normal) elif circle3D: centerPoint = circle3D.center radius = circle3D.radius sketchCircles(sketch, centerPoint, radius) else: classType = segment.classType() raise Exception('Unsupported pocket curve type for this sample script: ' + classType) mergeCoincidentPoints(sketch) # Give control back to Fusion so it can update the graphics. adsk.doEvents() def mergeCoincidentPoints(sketch: adsk.fusion.Sketch): ''' Merge sketchpoints that are coincident. ''' endPoints: list[adsk.fusion.SketchPoint] = [] # Get the end points of all lines and arcs. for skLine in sketch.sketchCurves.sketchLines: endPoints.append(skLine.startSketchPoint) endPoints.append(skLine.endSketchPoint) for skArc in sketch.sketchCurves.sketchArcs: endPoints.append(skArc.startSketchPoint) endPoints.append(skArc.endSketchPoint) # Check if the points are at the same location and add a constraint. for i in range(len(endPoints)): point1 = endPoints[i] if not point1 is None: for j in range(i+ 1, len(endPoints)): point2 = endPoints[j] if not point2 is None: if point1.geometry.isEqualTo(point2.geometry): point1.merge(point2) endPoints[i] = None endPoints[j] = None def sketchCircles(sketch: adsk.fusion.Sketch, centerPoint: adsk.core.Point3D, radius: float) -> adsk.fusion.SketchCircle: ''' Create a circle based on the points ''' circles = sketch.sketchCurves.sketchCircles circle = circles.addByCenterRadius(centerPoint, radius) return circle def sketchTwoPointArc(sketch: adsk.fusion.Sketch, centerPoint: adsk.core.Point3D, startPoint: adsk.core.Point3D, sweepAngle: float, normal: adsk.core.Vector3D) -> adsk.fusion.SketchArc: ''' Sketch a arc based on center, radius and sweepangle ''' arcs = sketch.sketchCurves.sketchArcs arc = arcs.addByCenterStartSweep(centerPoint, startPoint, sweepAngle) arcNormal = arc.geometry.normal # check whether the arc is drawn in the right direction if not arcNormal.z - normal.z < 0.000001 and arcNormal.y - normal.y < 0.000001 and arcNormal.x - normal.x < 0.000001: arc.deleteMe() arc = arcs.addByCenterStartSweep(centerPoint, startPoint, -sweepAngle) return arc def sketchLine(sketch: adsk.fusion.Sketch, startPoint: adsk.core.Point3D, endPoint: adsk.core.Point3D) -> adsk.fusion.SketchLine: ''' Sketch a straight line based on the starting and ending points ''' lines = sketch.sketchCurves.sketchLines line = lines.addByTwoPoints(startPoint, endPoint) return line ####################### COLORING ####################### def colorFaces(design: adsk.fusion.Design, faces: list[adsk.fusion.BRepFace], colorName: str, color: adsk.core.Color): ''' Color given BRepFaces ''' app = adsk.core.Application.get() # look for the color fusionMaterials = app.materialLibraries.itemByName('Fusion Appearance Library') newColor = design.appearances.itemByName(colorName) if not newColor: # Get the existing Red appearance. redColor = fusionMaterials.appearances.itemByName('Paint - Enamel Glossy (Red)') # Copy it to the design, giving it a new name. newColor = design.appearances.addByCopy(redColor, colorName) # Change the color of the default appearance to the provided one. theColor: adsk.core.ColorProperty = newColor.appearanceProperties.itemByName('Color') theColor.value = color # color given faces for face in faces: face.appearance = newColor # Give control back to Fusion so it can update the graphics. adsk.doEvents() ####################### CREATE SAMPLE PART ####################### def createSampleBody(component: adsk.fusion.Component) -> adsk.fusion.BRepBody: ''' Create a sample part for the script ''' # Get reference to the sketchs sketches = component.sketches # Get the extrude features Collection for the component extrudes = component.features.extrudeFeatures chamfers = component.features.chamferFeatures # create a cuiboid rectangle = sketches.add(component.xYConstructionPlane) rectangle.sketchCurves.sketchLines.addTwoPointRectangle(adsk.core.Point3D.create(0, 0, 0), adsk.core.Point3D.create(22.0, 15.0, 0)) blockExtrude = createExtrudeFeature(extrudes, rectangle, 2, adsk.fusion.FeatureOperations.NewBodyFeatureOperation) # create a simple hole holeOne = sketches.add(component.xYConstructionPlane) circleOne = [adsk.core.Circle3D.createByCenter(adsk.core.Point3D.create(3, 11.5, 2), adsk.core.Vector3D.create(0, 0, 1), 0.5)] drawSketchCurves(holeOne, circleOne) createExtrudeFeature(extrudes, holeOne, -2, adsk.fusion.FeatureOperations.CutFeatureOperation) # create another simple hole holeTwo = sketches.add(component.xYConstructionPlane) circleTwo = [adsk.core.Circle3D.createByCenter(adsk.core.Point3D.create(5, 11.5, 2), adsk.core.Vector3D.create(0, 0, 1), 0.5)] drawSketchCurves(holeTwo, circleTwo) createExtrudeFeature(extrudes, holeTwo, -2, adsk.fusion.FeatureOperations.CutFeatureOperation) # Give control back to Fusion so it can update the graphics. adsk.doEvents() # Create six counterbore holes sk = sketches.add(blockExtrude.endFaces[0]) skPoints = adsk.core.ObjectCollection.create() for i in range(1, 3): for j in range(1, 4): pstn = adsk.core.Point3D.create(3 * j, 3 * i, 0) skPoint = sk.sketchPoints.add(pstn) skPoints.add(skPoint) holes = component.features.holeFeatures counterBoreDiam = adsk.core.ValueInput.createByReal(2) counterBoreDepth = adsk.core.ValueInput.createByReal(1) holeDiam = adsk.core.ValueInput.createByReal(1) holeInput = holes.createCounterboreInput(holeDiam, counterBoreDiam, counterBoreDepth) holeInput.setAllExtent(adsk.fusion.ExtentDirections.PositiveExtentDirection) holeInput.setPositionBySketchPoints(skPoints) holeFeature = holes.add(holeInput) # Give control back to Fusion so it can update the graphics. adsk.doEvents() # Find the top edges of the holes to add a chamfer. topFace = blockExtrude.endFaces[0] chamferEdges = adsk.core.ObjectCollection.create() for cylinderFace in holeFeature.faces: if isinstance(cylinderFace.geometry, adsk.core.Cylinder): commonEdge = findCommonEdge(cylinderFace, topFace) if commonEdge: chamferEdges.add(commonEdge) # create the chamfer chamferInput = chamfers.createInput2() offset = adsk.core.ValueInput.createByReal(0.3) chamferInput.chamferEdgeSets.addEqualDistanceChamferEdgeSet(chamferEdges, offset, False) chamfers.add(chamferInput) # Give control back to Fusion so it can update the graphics. adsk.doEvents() # create a closed pocket pocketOne = sketches.add(component.xYConstructionPlane) geometries: list[adsk.core.Curve3D] = [] geometries.append(adsk.core.Line3D.create(adsk.core.Point3D.create(12, 1.5, 2),adsk.core.Point3D.create(13, 1.5, 2))) geometries.append(adsk.core.Line3D.create(adsk.core.Point3D.create(14, 2.5, 2),adsk.core.Point3D.create(14, 4.5, 2))) geometries.append(adsk.core.Line3D.create(adsk.core.Point3D.create(13, 5.5, 2),adsk.core.Point3D.create(12, 5.5, 2))) geometries.append(adsk.core.Line3D.create(adsk.core.Point3D.create(11, 4.5, 2),adsk.core.Point3D.create(11, 2.5, 2))) geometries.append(adsk.core.Arc3D.createByCenter(adsk.core.Point3D.create(12, 2.5, 2), adsk.core.Vector3D.create(0, 0, 1), adsk.core.Vector3D.create(-1, 0, 0), 1, 0, math.pi / 2)) geometries.append(adsk.core.Arc3D.createByCenter(adsk.core.Point3D.create(13, 2.5, 2), adsk.core.Vector3D.create(0, 0, 1), adsk.core.Vector3D.create(0, -1, 0), 1, 0, math.pi / 2)) geometries.append(adsk.core.Arc3D.createByCenter(adsk.core.Point3D.create(13, 4.5, 2), adsk.core.Vector3D.create(0, 0, 1), adsk.core.Vector3D.create(1, 0, 0), 1, 0, math.pi / 2)) geometries.append(adsk.core.Arc3D.createByCenter(adsk.core.Point3D.create(12, 4.5, 2), adsk.core.Vector3D.create(0, 0, 1), adsk.core.Vector3D.create(0, 1, 0), 1, 0, math.pi / 2)) drawSketchCurves(pocketOne, geometries) createExtrudeFeature(extrudes, pocketOne, -1, adsk.fusion.FeatureOperations.CutFeatureOperation) # create a open pocket pocketTwo = sketches.add(component.xYConstructionPlane) pocketTwoOutline: list[adsk.core.Curve3D] = [] pocketTwoOutline.append(adsk.core.Line3D.create(adsk.core.Point3D.create(12, 7.5, 2),adsk.core.Point3D.create(18, 7.5, 2))) pocketTwoOutline.append(adsk.core.Line3D.create(adsk.core.Point3D.create(19, 8.5, 2),adsk.core.Point3D.create(19, 11.5, 2))) pocketTwoOutline.append(adsk.core.Line3D.create(adsk.core.Point3D.create(18, 12.5, 2),adsk.core.Point3D.create(12, 12.5, 2))) pocketTwoOutline.append(adsk.core.Line3D.create(adsk.core.Point3D.create(11, 11.5, 2),adsk.core.Point3D.create(11, 8.5, 2))) pocketTwoOutline.append(adsk.core.Arc3D.createByCenter(adsk.core.Point3D.create(12, 8.5, 2), adsk.core.Vector3D.create(0, 0, 1), adsk.core.Vector3D.create(-1,0,0), 1, 0, math.pi / 2)) pocketTwoOutline.append(adsk.core.Arc3D.createByCenter(adsk.core.Point3D.create(18, 8.5, 2), adsk.core.Vector3D.create(0, 0, 1), adsk.core.Vector3D.create(0,-1,0), 1, 0, math.pi / 2)) pocketTwoOutline.append(adsk.core.Arc3D.createByCenter(adsk.core.Point3D.create(18, 11.5, 2), adsk.core.Vector3D.create(0, 0, 1), adsk.core.Vector3D.create(1,0,0), 1, 0, math.pi / 2)) pocketTwoOutline.append(adsk.core.Arc3D.createByCenter(adsk.core.Point3D.create(12, 11.5, 2), adsk.core.Vector3D.create(0, 0, 1), adsk.core.Vector3D.create(0,1,0), 1, 0, math.pi / 2)) drawSketchCurves(pocketTwo, pocketTwoOutline) createExtrudeFeature(extrudes, pocketTwo, -2, adsk.fusion.FeatureOperations.CutFeatureOperation) # create a pocket on the side pocketThree = sketches.add(component.xYConstructionPlane) pocketThreeOutline: list[adsk.core.Curve3D] = [] pocketThreeOutline.append(adsk.core.Line3D.create(adsk.core.Point3D.create(18, 0, 2),adsk.core.Point3D.create(22, 0, 2))) pocketThreeOutline.append(adsk.core.Line3D.create(adsk.core.Point3D.create(22, 0, 2),adsk.core.Point3D.create(22, 4.5, 2))) pocketThreeOutline.append(adsk.core.Line3D.create(adsk.core.Point3D.create(22, 4.5, 2),adsk.core.Point3D.create(19, 4.5, 2))) pocketThreeOutline.append(adsk.core.Line3D.create(adsk.core.Point3D.create(18, 3.5, 2),adsk.core.Point3D.create(18, 0, 2))) pocketThreeOutline.append(adsk.core.Arc3D.createByCenter(adsk.core.Point3D.create(19, 3.5, 2), adsk.core.Vector3D.create(0,0,1), adsk.core.Vector3D.create(0, 1, 0), 1, 0, math.pi / 2)) drawSketchCurves(pocketThree, pocketThreeOutline) createExtrudeFeature(extrudes, pocketThree, -1.5, adsk.fusion.FeatureOperations.CutFeatureOperation) # Give control back to Fusion so it can update the graphics. adsk.doEvents() # return the created body part = component.bRepBodies.item(0) return part def createExtrudeFeature(extrudeFeatures: adsk.fusion.ExtrudeFeatures, sketch: adsk.fusion.Sketch, height: float, operation: adsk.fusion.FeatureOperations) -> adsk.fusion.ExtrudeFeature: ''' Create an extrude feature ''' # Get the profile defined by the circle shape = sketch.profiles.item(0) # Define that the extent is a distance extent of 1 cm distance = adsk.core.ValueInput.createByReal(height) # Create the extrusion return extrudeFeatures.addSimple(shape, distance, operation) # Find the edge that connects to the input faces. def findCommonEdge(face1: adsk.fusion.BRepFace, face2: adsk.fusion.BRepFace) -> adsk.fusion.BRepEdge: # Checks to see if any of the edges of face1 connect to face2. edge: adsk.fusion.BRepEdge = None for edge in face1.edges: for face in edge.faces: if face == face2: return edge return None