This script for Autodesk Fusion rotates a body around a spiral track, repeatedly cutting it with a selected tool.
This was used successfully to create jaws for a scroll chuck. I didn't continue developing it once I achieved my goal.
import math | |
import traceback | |
import adsk.core, adsk.fusion | |
def spiral_cut(target, tool, pitch, stepAngle, revolutions): | |
# Create an ObjectCollection for the tool bodies | |
tool_collection = adsk.core.ObjectCollection.create() | |
tool_collection.add(tool) | |
# And for the target bodies | |
target_collection = adsk.core.ObjectCollection.create() | |
target_collection.add(target) | |
i = 0 | |
cum_angle = 0 | |
while i < 100 and cum_angle <= 2 * math.pi * revolutions: | |
i += 1 | |
# Combine operation | |
try: | |
combineFeatures = target.parentComponent.features.combineFeatures | |
combineInput = combineFeatures.createInput(target, tool_collection) | |
combineInput.isKeepToolBodies = True | |
combineInput.operation = adsk.fusion.FeatureOperations.CutFeatureOperation | |
combineFeatures.add(combineInput) | |
except RuntimeError as e: | |
if 'Compute Failed' in str(e): | |
pass # Ignore and continue | |
else: | |
raise | |
# Rotate along the track | |
# FIXME: Fails if our angle is 360 degrees | |
rotation = adsk.core.Matrix3D.create() | |
rotation.setToRotation( | |
stepAngle, | |
adsk.core.Vector3D.create(0, 0, 1), # around Z axis | |
adsk.core.Point3D.create(0, 0, 0) # origin | |
) | |
moveFeatures = target.parentComponent.features.moveFeatures | |
moveInput = moveFeatures.createInput(target_collection, rotation) | |
moveFeatures.add(moveInput) | |
cum_angle += stepAngle | |
# And translate | |
translation = adsk.core.Matrix3D.create() | |
translation.translation = adsk.core.Vector3D.create( | |
pitch * stepAngle / (2*math.pi) * math.cos(cum_angle + math.pi/2), | |
pitch * stepAngle / (2*math.pi) * math.sin(cum_angle + math.pi/2), | |
0 | |
) | |
moveFeatures = target.parentComponent.features.moveFeatures | |
moveInput = moveFeatures.createInput(target_collection, translation) | |
moveFeatures.add(moveInput) | |
def run(context): | |
global app, ui | |
app = adsk.core.Application.get() | |
ui = app.userInterface | |
try: | |
# Create a command definition | |
cmdDef = ui.commandDefinitions.itemById('spiralCutCmd') | |
if not cmdDef: | |
cmdDef = ui.commandDefinitions.addButtonDefinition( | |
'spiralCutCmd', | |
'Spiral Cut', | |
'Perform a spiral cut', | |
'' | |
) | |
# Add a command created event handler | |
onCommandCreated = SpiralCutCreatedHandler() | |
cmdDef.commandCreated.add(onCommandCreated) | |
handlers.append(onCommandCreated) # prevent GC | |
# Execute the command and keep script running while user interacts | |
cmdDef.execute() | |
adsk.autoTerminate(False) | |
except: | |
if ui: | |
ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) | |
class SpiralCutCreatedHandler(adsk.core.CommandCreatedEventHandler): | |
def notify(self, args): | |
global design | |
try: | |
eventArgs = adsk.core.CommandCreatedEventArgs.cast(args) | |
cmd = eventArgs.command | |
inputs = cmd.commandInputs | |
# Verify that a Fusion design is active. | |
design = adsk.fusion.Design.cast(app.activeProduct) | |
if not design: | |
ui.messageBox('The DESIGN workspace must be active when ' | |
'running this command.') | |
return | |
# Selection input for Jaw component | |
targetInput = inputs.addSelectionInput( | |
'targetInput', | |
'Target', | |
'Select the target body to cut' | |
) | |
targetInput.addSelectionFilter('SolidBodies') | |
toolInput = inputs.addSelectionInput( | |
'toolInput', | |
'Tool', | |
'Select the cutting tool body' | |
) | |
toolInput.addSelectionFilter('SolidBodies') | |
pitch = '4 mm' | |
pitchInput = inputs.addValueInput( | |
'pitchInput', | |
'Pitch', | |
design.unitsManager.defaultLengthUnits, | |
adsk.core.ValueInput.createByString(pitch) | |
) | |
stepAngle = '60 deg' | |
stepAngleInput = inputs.addValueInput( | |
'stepAngleInput', | |
'Step Angle', | |
'deg', | |
adsk.core.ValueInput.createByString(stepAngle) | |
) | |
revolutions = 7 | |
revolutionsInput = inputs.addStringValueInput( | |
'revolutionsInput', | |
'Revolutions', | |
str(revolutions) | |
) | |
# Wire up additional event handlers | |
onExecute = SpiralCutExecuteHandler() | |
cmd.execute.add(onExecute) | |
handlers.append(onExecute) | |
onDestroy = SpiralCutDestroyHandler() | |
cmd.destroy.add(onDestroy) | |
handlers.append(onDestroy) | |
except: | |
if ui: | |
ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) | |
class SpiralCutExecuteHandler(adsk.core.CommandEventHandler): | |
def notify(self, args): | |
try: | |
eventArgs = adsk.core.CommandEventArgs.cast(args) | |
cmd = eventArgs.command | |
inputs = cmd.commandInputs | |
def findInputByName(name): | |
for input in inputs: | |
if input.id == name: | |
return input | |
return None | |
target = findInputByName('targetInput').selection(0).entity | |
tool = findInputByName('toolInput').selection(0).entity | |
pitch = findInputByName('pitchInput').value | |
stepAngle = findInputByName('stepAngleInput').value | |
revolutions = int(findInputByName('revolutionsInput').value) | |
firstIndex = design.timeline.markerPosition | |
try: | |
spiral_cut( | |
target, | |
tool, | |
pitch, | |
stepAngle, | |
revolutions | |
) | |
except: | |
# Display the dialog, but continue to make timeline group | |
if ui: | |
ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) | |
lastIndex = design.timeline.markerPosition - 1 | |
ui.messageBox(f'{lastIndex - firstIndex + 1} timeline items were added') | |
if lastIndex > firstIndex: | |
timelineGroup = design.timeline.timelineGroups.add(\ | |
firstIndex, lastIndex) | |
timelineGroup.name = 'Spiral Cut' | |
except: | |
if ui: | |
ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) | |
class SpiralCutDestroyHandler(adsk.core.CommandEventHandler): | |
def notify(self, args): | |
try: | |
adsk.terminate() | |
except: | |
if ui: | |
ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) | |
# List to keep handlers alive | |
handlers = [] |