#Author-Brian Ekins #Description-Creates a spur gear component. # AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. AUTODESK SPECIFICALLY # DISCLAIMS ANY IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. # AUTODESK, INC. DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE # UNINTERRUPTED OR ERROR FREE. import adsk.core, adsk.fusion, adsk.cam, traceback import math # Globals _app = adsk.core.Application.cast(None) _ui = adsk.core.UserInterface.cast(None) _units = '' # Command inputs _imgInputEnglish = adsk.core.ImageCommandInput.cast(None) _imgInputMetric = adsk.core.ImageCommandInput.cast(None) _standard = adsk.core.DropDownCommandInput.cast(None) _pressureAngle = adsk.core.DropDownCommandInput.cast(None) _pressureAngleCustom = adsk.core.ValueCommandInput.cast(None) _backlash = adsk.core.ValueCommandInput.cast(None) _diaPitch = adsk.core.ValueCommandInput.cast(None) _module = adsk.core.ValueCommandInput.cast(None) _numTeeth = adsk.core.StringValueCommandInput.cast(None) _rootFilletRad = adsk.core.ValueCommandInput.cast(None) _thickness = adsk.core.ValueCommandInput.cast(None) _holeDiam = adsk.core.ValueCommandInput.cast(None) _pitchDiam = adsk.core.TextBoxCommandInput.cast(None) _errMessage = adsk.core.TextBoxCommandInput.cast(None) _handlers = [] def run(context): try: global _app, _ui _app = adsk.core.Application.get() _ui = _app.userInterface # Create a command definition and add a button to the CREATE panel. cmdDef = _ui.commandDefinitions.addButtonDefinition('adskSpurGearPythonAddIn', 'Spur Gear', 'Creates a spur gear component', 'Resources/SpurGear') createPanel = _ui.allToolbarPanels.itemById('SolidCreatePanel') gearButton = createPanel.controls.addCommand(cmdDef) # Connect to the command created event. onCommandCreated = GearCommandCreatedHandler() cmdDef.commandCreated.add(onCommandCreated) _handlers.append(onCommandCreated) if context['IsApplicationStartup'] == False: _ui.messageBox('The "Spur Gear" command has been added\nto the CREATE panel of the MODEL workspace.') except: if _ui: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) def stop(context): try: createPanel = _ui.allToolbarPanels.itemById('SolidCreatePanel') gearButton = createPanel.controls.itemById('adskSpurGearPythonAddIn') if gearButton: gearButton.deleteMe() cmdDef = _ui.commandDefinitions.itemById('adskSpurGearPythonAddIn') if cmdDef: cmdDef.deleteMe() except: if _ui: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) # Verfies that a value command input has a valid expression and returns the # value if it does. Otherwise it returns False. This works around a # problem where when you get the value from a ValueCommandInput it causes the # current expression to be evaluated and updates the display. Some new functionality # is being added in the future to the ValueCommandInput object that will make # this easier and should make this function obsolete. def getCommandInputValue(commandInput, unitType): try: valCommandInput = adsk.core.ValueCommandInput.cast(commandInput) if not valCommandInput: return (False, 0) # Verify that the expression is valid. des = adsk.fusion.Design.cast(_app.activeProduct) unitsMgr = des.unitsManager if unitsMgr.isValidExpression(valCommandInput.expression, unitType): value = unitsMgr.evaluateExpression(valCommandInput.expression, unitType) return (True, value) else: return (False, 0) except: if _ui: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) # Event handler for the commandCreated event. class GearCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): def __init__(self): super().__init__() def notify(self, args): try: eventArgs = adsk.core.CommandCreatedEventArgs.cast(args) # Verify that a Fusion design is active. des = adsk.fusion.Design.cast(_app.activeProduct) if not des: _ui.messageBox('A Fusion design must be active when invoking this command.') return() defaultUnits = des.unitsManager.defaultLengthUnits # Determine whether to use inches or millimeters as the intial default. global _units if defaultUnits == 'in' or defaultUnits == 'ft': _units = 'in' else: _units = 'mm' # Define the default values and get the previous values from the attributes. if _units == 'in': standard = 'English' else: standard = 'Metric' standardAttrib = des.attributes.itemByName('SpurGear', 'standard') if standardAttrib: standard = standardAttrib.value if standard == 'English': _units = 'in' else: _units = 'mm' pressureAngle = '20 deg' pressureAngleAttrib = des.attributes.itemByName('SpurGear', 'pressureAngle') if pressureAngleAttrib: pressureAngle = pressureAngleAttrib.value pressureAngleCustom = 20 * (math.pi/180.0) pressureAngleCustomAttrib = des.attributes.itemByName('SpurGear', 'pressureAngleCustom') if pressureAngleCustomAttrib: pressureAngleCustom = float(pressureAngleCustomAttrib.value) diaPitch = '2' diaPitchAttrib = des.attributes.itemByName('SpurGear', 'diaPitch') if diaPitchAttrib: diaPitch = diaPitchAttrib.value metricModule = 25.4 / float(diaPitch) backlash = '0' backlashAttrib = des.attributes.itemByName('SpurGear', 'backlash') if backlashAttrib: backlash = backlashAttrib.value numTeeth = '24' numTeethAttrib = des.attributes.itemByName('SpurGear', 'numTeeth') if numTeethAttrib: numTeeth = numTeethAttrib.value rootFilletRad = str(.0625 * 2.54) rootFilletRadAttrib = des.attributes.itemByName('SpurGear', 'rootFilletRad') if rootFilletRadAttrib: rootFilletRad = rootFilletRadAttrib.value thickness = str(0.5 * 2.54) thicknessAttrib = des.attributes.itemByName('SpurGear', 'thickness') if thicknessAttrib: thickness = thicknessAttrib.value holeDiam = str(0.5 * 2.54) holeDiamAttrib = des.attributes.itemByName('SpurGear', 'holeDiam') if holeDiamAttrib: holeDiam = holeDiamAttrib.value cmd = eventArgs.command cmd.isExecutedWhenPreEmpted = False inputs = cmd.commandInputs global _standard, _pressureAngle, _pressureAngleCustom, _diaPitch, _pitch, _module, _numTeeth, _rootFilletRad, _thickness, _holeDiam, _pitchDiam, _backlash, _imgInputEnglish, _imgInputMetric, _errMessage # Define the command dialog. _imgInputEnglish = inputs.addImageCommandInput('gearImageEnglish', '', 'Resources/GearEnglish.png') _imgInputEnglish.isFullWidth = True _imgInputMetric = inputs.addImageCommandInput('gearImageMetric', '', 'Resources/GearMetric.png') _imgInputMetric.isFullWidth = True _standard = inputs.addDropDownCommandInput('standard', 'Standard', adsk.core.DropDownStyles.TextListDropDownStyle) if standard == "English": _standard.listItems.add('English', True) _standard.listItems.add('Metric', False) _imgInputMetric.isVisible = False else: _standard.listItems.add('English', False) _standard.listItems.add('Metric', True) _imgInputEnglish.isVisible = False _pressureAngle = inputs.addDropDownCommandInput('pressureAngle', 'Pressure Angle', adsk.core.DropDownStyles.TextListDropDownStyle) if pressureAngle == '14.5 deg': _pressureAngle.listItems.add('14.5 deg', True) else: _pressureAngle.listItems.add('14.5 deg', False) if pressureAngle == '20 deg': _pressureAngle.listItems.add('20 deg', True) else: _pressureAngle.listItems.add('20 deg', False) if pressureAngle == '25 deg': _pressureAngle.listItems.add('25 deg', True) else: _pressureAngle.listItems.add('25 deg', False) if pressureAngle == 'Custom': _pressureAngle.listItems.add('Custom', True) else: _pressureAngle.listItems.add('Custom', False) _pressureAngleCustom = inputs.addValueInput('pressureAngleCustom', 'Custom Angle', 'deg', adsk.core.ValueInput.createByReal(pressureAngleCustom)) if pressureAngle != 'Custom': _pressureAngleCustom.isVisible = False _diaPitch = inputs.addValueInput('diaPitch', 'Diametral Pitch', '', adsk.core.ValueInput.createByString(diaPitch)) _module = inputs.addValueInput('module', 'Module', '', adsk.core.ValueInput.createByReal(metricModule)) if standard == 'English': _module.isVisible = False elif standard == 'Metric': _diaPitch.isVisible = False _numTeeth = inputs.addStringValueInput('numTeeth', 'Number of Teeth', numTeeth) _backlash = inputs.addValueInput('backlash', 'Backlash', _units, adsk.core.ValueInput.createByReal(float(backlash))) _rootFilletRad = inputs.addValueInput('rootFilletRad', 'Root Fillet Radius', _units, adsk.core.ValueInput.createByReal(float(rootFilletRad))) _thickness = inputs.addValueInput('thickness', 'Gear Thickness', _units, adsk.core.ValueInput.createByReal(float(thickness))) _holeDiam = inputs.addValueInput('holeDiam', 'Hole Diameter', _units, adsk.core.ValueInput.createByReal(float(holeDiam))) _pitchDiam = inputs.addTextBoxCommandInput('pitchDiam', 'Pitch Diameter', '', 1, True) _errMessage = inputs.addTextBoxCommandInput('errMessage', '', '', 2, True) _errMessage.isFullWidth = True # Connect to the command related events. onExecute = GearCommandExecuteHandler() cmd.execute.add(onExecute) _handlers.append(onExecute) onInputChanged = GearCommandInputChangedHandler() cmd.inputChanged.add(onInputChanged) _handlers.append(onInputChanged) onValidateInputs = GearCommandValidateInputsHandler() cmd.validateInputs.add(onValidateInputs) _handlers.append(onValidateInputs) except: if _ui: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) # Event handler for the execute event. class GearCommandExecuteHandler(adsk.core.CommandEventHandler): def __init__(self): super().__init__() def notify(self, args): try: eventArgs = adsk.core.CommandEventArgs.cast(args) if _standard.selectedItem.name == 'English': diaPitch = _diaPitch.value elif _standard.selectedItem.name == 'Metric': diaPitch = 25.4 / _module.value # Save the current values as attributes. des = adsk.fusion.Design.cast(_app.activeProduct) attribs = des.attributes attribs.add('SpurGear', 'standard', _standard.selectedItem.name) attribs.add('SpurGear', 'pressureAngle', _pressureAngle.selectedItem.name) attribs.add('SpurGear', 'pressureAngleCustom', str(_pressureAngleCustom.value)) attribs.add('SpurGear', 'diaPitch', str(diaPitch)) attribs.add('SpurGear', 'numTeeth', str(_numTeeth.value)) attribs.add('SpurGear', 'rootFilletRad', str(_rootFilletRad.value)) attribs.add('SpurGear', 'thickness', str(_thickness.value)) attribs.add('SpurGear', 'holeDiam', str(_holeDiam.value)) attribs.add('SpurGear', 'backlash', str(_backlash.value)) # Get the current values. if _pressureAngle.selectedItem.name == 'Custom': pressureAngle = _pressureAngleCustom.value else: if _pressureAngle.selectedItem.name == '14.5 deg': pressureAngle = 14.5 * (math.pi/180) elif _pressureAngle.selectedItem.name == '20 deg': pressureAngle = 20.0 * (math.pi/180) elif _pressureAngle.selectedItem.name == '25 deg': pressureAngle = 25.0 * (math.pi/180) numTeeth = int(_numTeeth.value) rootFilletRad = _rootFilletRad.value thickness = _thickness.value holeDiam = _holeDiam.value backlash = _backlash.value # Create the gear. gearComp = drawGear(des, diaPitch, numTeeth, thickness, rootFilletRad, pressureAngle, backlash, holeDiam) if gearComp: if _standard.selectedItem.name == 'English': desc = 'Spur Gear; Diametrial Pitch: ' + str(diaPitch) + '; ' elif _standard.selectedItem.name == 'Metric': desc = 'Spur Gear; Module: ' + str(25.4 / diaPitch) + '; ' desc += 'Num Teeth: ' + str(numTeeth) + '; ' desc += 'Pressure Angle: ' + str(pressureAngle * (180/math.pi)) + '; ' desc += 'Backlash: ' + des.unitsManager.formatInternalValue(backlash, _units, True) gearComp.description = desc except: if _ui: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) # Event handler for the inputChanged event. class GearCommandInputChangedHandler(adsk.core.InputChangedEventHandler): def __init__(self): super().__init__() def notify(self, args): try: eventArgs = adsk.core.InputChangedEventArgs.cast(args) changedInput = eventArgs.input global _units if changedInput.id == 'standard': if _standard.selectedItem.name == 'English': _imgInputMetric.isVisible = False _imgInputEnglish.isVisible = True _diaPitch.isVisible = True _module.isVisible = False _diaPitch.value = 25.4 / _module.value _units = 'in' elif _standard.selectedItem.name == 'Metric': _imgInputMetric.isVisible = True _imgInputEnglish.isVisible = False _diaPitch.isVisible = False _module.isVisible = True _module.value = 25.4 / _diaPitch.value _units = 'mm' # Set each one to it's current value because otherwised if the user # has edited it, the value won't update in the dialog because # apparently it remembers the units when the value was edited. # Setting the value using the API resets this. _backlash.value = _backlash.value _backlash.unitType = _units _rootFilletRad.value = _rootFilletRad.value _rootFilletRad.unitType = _units _thickness.value = _thickness.value _thickness.unitType = _units _holeDiam.value = _holeDiam.value _holeDiam.unitType = _units # Update the pitch diameter value. diaPitch = None if _standard.selectedItem.name == 'English': result = getCommandInputValue(_diaPitch, '') if result[0]: diaPitch = result[1] elif _standard.selectedItem.name == 'Metric': result = getCommandInputValue(_module, '') if result[0]: diaPitch = 25.4 / result[1] if not diaPitch == None: if _numTeeth.value.isdigit(): numTeeth = int(_numTeeth.value) pitchDia = numTeeth/diaPitch # The pitch dia has been calculated in inches, but this expects cm as the input units. des = adsk.fusion.Design.cast(_app.activeProduct) pitchDiaText = des.unitsManager.formatInternalValue(pitchDia * 2.54, _units, True) _pitchDiam.text = pitchDiaText else: _pitchDiam.text = '' else: _pitchDiam.text = '' if changedInput.id == 'pressureAngle': if _pressureAngle.selectedItem.name == 'Custom': _pressureAngleCustom.isVisible = True else: _pressureAngleCustom.isVisible = False except: if _ui: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) # Event handler for the validateInputs event. class GearCommandValidateInputsHandler(adsk.core.ValidateInputsEventHandler): def __init__(self): super().__init__() def notify(self, args): try: eventArgs = adsk.core.ValidateInputsEventArgs.cast(args) _errMessage.text = '' # Verify that at lesat 4 teath are specified. if not _numTeeth.value.isdigit(): _errMessage.text = 'The number of teeth must be a whole number.' eventArgs.areInputsValid = False return else: numTeeth = int(_numTeeth.value) if numTeeth < 4: _errMessage.text = 'The number of teeth must be 4 or more.' eventArgs.areInputsValid = False return # Calculate some of the gear sizes to use in validation. if _standard.selectedItem.name == 'English': result = getCommandInputValue(_diaPitch, '') if result[0] == False: eventArgs.areInputsValid = False return else: diaPitch = result[1] elif _standard.selectedItem.name == 'Metric': result = getCommandInputValue(_module, '') if result[0] == False: eventArgs.areInputsValid = False return else: diaPitch = 25.4 / result[1] diametralPitch = diaPitch / 2.54 pitchDia = numTeeth / diametralPitch if (diametralPitch < (20 *(math.pi/180))-0.000001): dedendum = 1.157 / diametralPitch else: circularPitch = math.pi / diametralPitch if circularPitch >= 20: dedendum = 1.25 / diametralPitch else: dedendum = (1.2 / diametralPitch) + (.002 * 2.54) rootDia = pitchDia - (2 * dedendum) if _pressureAngle.selectedItem.name == 'Custom': pressureAngle = _pressureAngleCustom.value else: if _pressureAngle.selectedItem.name == '14.5 deg': pressureAngle = 14.5 * (math.pi/180) elif _pressureAngle.selectedItem.name == '20 deg': pressureAngle = 20.0 * (math.pi/180) elif _pressureAngle.selectedItem.name == '25 deg': pressureAngle = 25.0 * (math.pi/180) baseCircleDia = pitchDia * math.cos(pressureAngle) baseCircleCircumference = 2 * math.pi * (baseCircleDia / 2) des = adsk.fusion.Design.cast(_app.activeProduct) result = getCommandInputValue(_holeDiam, _units) if result[0] == False: eventArgs.areInputsValid = False return else: holeDiam = result[1] if holeDiam >= (rootDia - 0.01): _errMessage.text = 'The center hole diameter is too large. It must be less than ' + des.unitsManager.formatInternalValue(rootDia - 0.01, _units, True) eventArgs.areInputsValid = False return toothThickness = baseCircleCircumference / (numTeeth * 2) if _rootFilletRad.value > toothThickness * .4: _errMessage.text = 'The root fillet radius is too large. It must be less than ' + des.unitsManager.formatInternalValue(toothThickness * .4, _units, True) eventArgs.areInputsValid = False return except: if _ui: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) # Calculate points along an involute curve. def involutePoint(baseCircleRadius, distFromCenterToInvolutePoint): try: # Calculate the other side of the right-angle triangle defined by the base circle and the current distance radius. # This is also the length of the involute chord as it comes off of the base circle. triangleSide = math.sqrt(math.pow(distFromCenterToInvolutePoint,2) - math.pow(baseCircleRadius,2)) # Calculate the angle of the involute. alpha = triangleSide / baseCircleRadius # Calculate the angle where the current involute point is. theta = alpha - math.acos(baseCircleRadius / distFromCenterToInvolutePoint) # Calculate the coordinates of the involute point. x = distFromCenterToInvolutePoint * math.cos(theta) y = distFromCenterToInvolutePoint * math.sin(theta) # Create a point to return. return adsk.core.Point3D.create(x, y, 0) except: if _ui: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) # Builds a spur gear. def drawGear(design, diametralPitch, numTeeth, thickness, rootFilletRad, pressureAngle, backlash, holeDiam): try: # The diametral pitch is specified in inches but everthing # here expects all distances to be in centimeters, so convert # for the gear creation. diametralPitch = diametralPitch / 2.54 # Compute the various values for a gear. pitchDia = numTeeth / diametralPitch #addendum = 1.0 / diametralPitch if (diametralPitch < (20 *(math.pi/180))-0.000001): dedendum = 1.157 / diametralPitch else: circularPitch = math.pi / diametralPitch if circularPitch >= 20: dedendum = 1.25 / diametralPitch else: dedendum = (1.2 / diametralPitch) + (.002 * 2.54) rootDia = pitchDia - (2 * dedendum) baseCircleDia = pitchDia * math.cos(pressureAngle) outsideDia = (numTeeth + 2) / diametralPitch # Create a new component by creating an occurrence. occs = design.rootComponent.occurrences mat = adsk.core.Matrix3D.create() newOcc = occs.addNewComponent(mat) newComp = adsk.fusion.Component.cast(newOcc.component) # Create a new sketch. sketches = newComp.sketches xyPlane = newComp.xYConstructionPlane baseSketch = sketches.add(xyPlane) # Draw a circle for the base. baseSketch.sketchCurves.sketchCircles.addByCenterRadius(adsk.core.Point3D.create(0,0,0), rootDia/2.0) # Draw a circle for the center hole, if the value is greater than 0. prof = adsk.fusion.Profile.cast(None) if holeDiam - (_app.pointTolerance * 2) > 0: baseSketch.sketchCurves.sketchCircles.addByCenterRadius(adsk.core.Point3D.create(0,0,0), holeDiam/2.0) # Find the profile that uses both circles. for prof in baseSketch.profiles: if prof.profileLoops.count == 2: break else: # Use the single profile. prof = baseSketch.profiles.item(0) #### Extrude the circle to create the base of the gear. # Create an extrusion input to be able to define the input needed for an extrusion # while specifying the profile and that a new component is to be created extrudes = newComp.features.extrudeFeatures extInput = extrudes.createInput(prof, adsk.fusion.FeatureOperations.NewBodyFeatureOperation) # Define that the extent is a distance extent of 5 cm. distance = adsk.core.ValueInput.createByReal(thickness) extInput.setDistanceExtent(False, distance) # Create the extrusion. baseExtrude = extrudes.add(extInput) # Create a second sketch for the tooth. toothSketch = sketches.add(xyPlane) # Calculate points along the involute curve. involutePointCount = 15; involuteIntersectionRadius = baseCircleDia / 2.0 involutePoints = [] involuteSize = (outsideDia - baseCircleDia) / 2.0 for i in range(0, involutePointCount): involuteIntersectionRadius = (baseCircleDia / 2.0) + ((involuteSize / (involutePointCount - 1)) * i) newPoint = involutePoint(baseCircleDia / 2.0, involuteIntersectionRadius) involutePoints.append(newPoint) # Get the point along the tooth that's at the pictch diameter and then # calculate the angle to that point. pitchInvolutePoint = involutePoint(baseCircleDia / 2.0, pitchDia / 2.0) pitchPointAngle = math.atan(pitchInvolutePoint.y / pitchInvolutePoint.x) # Determine the angle defined by the tooth thickness as measured at # the pitch diameter circle. toothThicknessAngle = (2 * math.pi) / (2 * numTeeth) # Determine the angle needed for the specified backlash. backlashAngle = (backlash / (pitchDia / 2.0)) * .25 # Determine the angle to rotate the curve. rotateAngle = -((toothThicknessAngle/2) + pitchPointAngle - backlashAngle) # Rotate the involute so the middle of the tooth lies on the x axis. cosAngle = math.cos(rotateAngle) sinAngle = math.sin(rotateAngle) for i in range(0, involutePointCount): newX = involutePoints[i].x * cosAngle - involutePoints[i].y * sinAngle newY = involutePoints[i].x * sinAngle + involutePoints[i].y * cosAngle involutePoints[i] = adsk.core.Point3D.create(newX, newY, 0) # Create a new set of points with a negated y. This effectively mirrors the original # points about the X axis. involute2Points = [] for i in range(0, involutePointCount): involute2Points.append(adsk.core.Point3D.create(involutePoints[i].x, -involutePoints[i].y, 0)) curve1Dist = [] curve1Angle = [] for i in range(0, involutePointCount): curve1Dist.append(math.sqrt(involutePoints[i].x * involutePoints[i].x + involutePoints[i].y * involutePoints[i].y)) curve1Angle.append(math.atan(involutePoints[i].y / involutePoints[i].x)) curve2Dist = [] curve2Angle = [] for i in range(0, involutePointCount): curve2Dist.append(math.sqrt(involute2Points[i].x * involute2Points[i].x + involute2Points[i].y * involute2Points[i].y)) curve2Angle.append(math.atan(involute2Points[i].y / involute2Points[i].x)) toothSketch.isComputeDeferred = True # Create and load an object collection with the points. pointSet = adsk.core.ObjectCollection.create() for i in range(0, involutePointCount): pointSet.add(involutePoints[i]) # Create the first spline. spline1 = toothSketch.sketchCurves.sketchFittedSplines.add(pointSet) # Add the involute points for the second spline to an ObjectCollection. pointSet = adsk.core.ObjectCollection.create() for i in range(0, involutePointCount): pointSet.add(involute2Points[i]) # Create the second spline. spline2 = toothSketch.sketchCurves.sketchFittedSplines.add(pointSet) # Draw the arc for the top of the tooth. midPoint = adsk.core.Point3D.create((outsideDia / 2), 0, 0) toothSketch.sketchCurves.sketchArcs.addByThreePoints(spline1.endSketchPoint, midPoint, spline2.endSketchPoint) # Check to see if involute goes down to the root or not. If not, then # create lines to connect the involute to the root. if( baseCircleDia < rootDia ): toothSketch.sketchCurves.sketchLines.addByTwoPoints(spline2.startSketchPoint, spline1.startSketchPoint) else: rootPoint1 = adsk.core.Point3D.create((rootDia / 2 - 0.001) * math.cos(curve1Angle[0] ), (rootDia / 2) * math.sin(curve1Angle[0]), 0) line1 = toothSketch.sketchCurves.sketchLines.addByTwoPoints(rootPoint1, spline1.startSketchPoint) rootPoint2 = adsk.core.Point3D.create((rootDia / 2 - 0.001) * math.cos(curve2Angle[0]), (rootDia / 2) * math.sin(curve2Angle[0]), 0) line2 = toothSketch.sketchCurves.sketchLines.addByTwoPoints(rootPoint2, spline2.startSketchPoint) baseLine = toothSketch.sketchCurves.sketchLines.addByTwoPoints(line1.startSketchPoint, line2.startSketchPoint) # Make the lines tangent to the spline so the root fillet will behave correctly. line1.isFixed = True line2.isFixed = True toothSketch.geometricConstraints.addTangent(spline1, line1) toothSketch.geometricConstraints.addTangent(spline2, line2) toothSketch.isComputeDeferred = False ### Extrude the tooth. # Get the profile defined by the tooth. prof = toothSketch.profiles.item(0) # Create an extrusion input to be able to define the input needed for an extrusion # while specifying the profile and that a new component is to be created extInput = extrudes.createInput(prof, adsk.fusion.FeatureOperations.JoinFeatureOperation) # Define that the extent is a distance extent of 5 cm. distance = adsk.core.ValueInput.createByReal(thickness) extInput.setDistanceExtent(False, distance) # Create the extrusion. toothExtrude = extrudes.add(extInput) baseFillet = None if rootFilletRad > 0: ### Find the edges between the base cylinder and the tooth. # Get the outer cylindrical face from the base extrusion by checking the number # of edges and if it's 2 get the other one. cylFace = baseExtrude.sideFaces.item(0) if cylFace.edges.count == 2: cylFace = baseExtrude.sideFaces.item(1) # Get the two linear edges, which are the connection between the cylinder and tooth. edges = adsk.core.ObjectCollection.create() for edge in cylFace.edges: if isinstance(edge.geometry, adsk.core.Line3D): edges.add(edge) # Create a fillet input to be able to define the input needed for a fillet. fillets = newComp.features.filletFeatures; filletInput = fillets.createInput() # Define that the extent is a distance extent of 5 cm. radius = adsk.core.ValueInput.createByReal(rootFilletRad) filletInput.addConstantRadiusEdgeSet(edges, radius, False) # Create the extrusion. baseFillet = fillets.add(filletInput) # Create a pattern of the tooth extrude and the base fillet. circularPatterns = newComp.features.circularPatternFeatures entities = adsk.core.ObjectCollection.create() entities.add(toothExtrude) if baseFillet: entities.add(baseFillet) cylFace = baseExtrude.sideFaces.item(0) patternInput = circularPatterns.createInput(entities, cylFace) numTeethInput = adsk.core.ValueInput.createByString(str(numTeeth)) patternInput.quantity = numTeethInput patternInput.patternComputeOption = adsk.fusion.PatternComputeOptions.IdenticalPatternCompute pattern = circularPatterns.add(patternInput) # Create an extra sketch that contains a circle of the diametral pitch. diametralPitchSketch = sketches.add(xyPlane) diametralPitchCircle = diametralPitchSketch.sketchCurves.sketchCircles.addByCenterRadius(adsk.core.Point3D.create(0,0,0), pitchDia/2.0) diametralPitchCircle.isConstruction = True diametralPitchCircle.isFixed = True # Group everything used to create the gear in the timeline. timelineGroups = design.timeline.timelineGroups newOccIndex = newOcc.timelineObject.index pitchSketchIndex = diametralPitchSketch.timelineObject.index # ui.messageBox("Indices: " + str(newOccIndex) + ", " + str(pitchSketchIndex)) timelineGroup = timelineGroups.add(newOccIndex, pitchSketchIndex) timelineGroup.name = 'Spur Gear' # Add an attribute to the component with all of the input values. This might # be used in the future to be able to edit the gear. gearValues = {} gearValues['diametralPitch'] = str(diametralPitch * 2.54) gearValues['numTeeth'] = str(numTeeth) gearValues['thickness'] = str(thickness) gearValues['rootFilletRad'] = str(rootFilletRad) gearValues['pressureAngle'] = str(pressureAngle) gearValues['holeDiam'] = str(holeDiam) gearValues['backlash'] = str(backlash) attrib = newComp.attributes.add('SpurGear', 'Values',str(gearValues)) newComp.name = 'Spur Gear (' + str(numTeeth) + ' teeth)' return newComp except Exception as error: _ui.messageBox("drawGear Failed : " + str(error)) return None