Hello,
You have to edit the file ShaperUtilities.py and move all the global variables definitions at the top the function in which there are declared.
Here is the modified script:
#Author- Brian Ekins
#Description- Various utilities to make it easy to use Fusion 360 designs with the Shaper Origin router.
import adsk.core, adsk.fusion, traceback, math, tempfile, os
from .MyGeomMath import getSVG, getLayoutSVG, MyBoard, createCompositesFromSketch, createCompositesFromFaces, createCompositesFromProfiles, setLogFile
_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)
_appPath = os.path.dirname(os.path.abspath(__file__))
_handlers = []
_version = '1.4.1'
_firstActivate = False
_logFile = None
_isBeta = False
# Command inputs.
_topLogo = adsk.core.ImageCommandInput.cast(None)
_typeList = adsk.core.DropDownCommandInput.cast(None)
_selectInput = adsk.core.SelectionCommandInput.cast(None)
_textNotes = adsk.core.TextBoxCommandInput.cast(None)
_advanced = adsk.core.BoolValueCommandInput.cast(None)
_shaperLink = adsk.core.TextBoxCommandInput.cast(None)
_versionText = adsk.core.TextBoxCommandInput.cast(None)
#_showOvercuts = adsk.core.BoolValueCommandInput.cast(None)
#_solidBodyPrompt = 'Select a "top" face on the body to export.'
_solidBodyPrompt = 'Select a single face to define the top cutting surface of a shape, all details cuttable from the top will be exported as an appropriately color coded SVG file.'
_layoutsPrompt = 'Select bodies lying on the X-Y plane.'
_profilePrompt = 'Select the profiles within a single sketch to be exported. Smart color coding is not active, each profilie is treated as a <i>cut outside</i> shape. Plan on changing the cut types on the tool.'
_sketchPrompt = 'Select a sketch to export. The entire contents of the sketch will be exported.'
_facePrompt = 'Selected parallel faces from the same body will be exported. Smart color coding is not active and each face is treated as a <i>cut outside</i> shape. Plan on changing the cut types on the tool.'
_shaperLinkText = '<div align="center"><a href="http:shapertools.com">www.shapertools.com</a></div>'
# Event handler for the commandCreated event.
class ExportCommandCreatedEventHandler(adsk.core.CommandCreatedEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
global _isBeta
global _topLogo
global _typeList
global _showOvercuts
global _selectInput
global _textNotes
global _advanced
global _shaperLink
global _versionText
global _firstActivate
try:
eventArgs = adsk.core.CommandCreatedEventArgs.cast(args)
cmd = eventArgs.command
cmd.helpFile = 'Resources/Help/ExportToOriginHelp.html'
inputs = cmd.commandInputs
# Set the default values.
advancedState = False
isAdvanced = False
typeState = 'Single Solid Body'
# Read the current defaults:
if os.path.isfile(os.path.join(_appPath, 'defaults.dat')):
file = open(os.path.join(_appPath, 'defaults.dat'))
data = file.readlines()[0]
file.close()
settings = data.split(';')
for setting in settings:
settingData = setting.split(':')
if settingData[0] == 'advanced':
if settingData[1] == 'True':
isAdvanced = True
else:
isAdvanced = False
elif settingData[0] == 'type':
typeState = settingData[1]
elif settingData[0] == 'beta':
if settingData[1] == 'True':
_isBeta = True
else:
_isBeta = False
# advancedState = data.split(';')[0].split(':')[1]
# if advancedState == 'True':
# isAdvanced = True
# else:
# isAdvanced = False
# typeState = data.split(';')[1].split(':')[1]
# Set the size of the dialog.
cmd.setDialogInitialSize(425, 275)
cmd.setDialogMinimumSize(425, 275)
_topLogo = inputs.addImageCommandInput('image', '', 'Resources/shaperExport/ShaperOriginBlack_sm.png')
_topLogo.isVisible = True
# Get the type of input.
_typeList = inputs.addDropDownCommandInput('typeList', 'Input type', adsk.core.DropDownStyles.LabeledIconDropDownStyle)
if typeState == 'Single Solid Body':
_typeList.listItems.add('Single Solid Body', True, 'Resources/SingleBody')
else:
_typeList.listItems.add('Single Solid Body', False, 'Resources/SingleBody')
if typeState == 'Solid Bodies Layout':
_typeList.listItems.add('Solid Bodies Layout', True, 'Resources/Layouts')
else:
_typeList.listItems.add('Solid Bodies Layout', False, 'Resources/Layouts')
if typeState == 'Faces':
_typeList.listItems.add('Faces', True, 'Resources/Faces')
else:
_typeList.listItems.add('Faces', False, 'Resources/Faces')
if typeState == 'Entire Sketch':
_typeList.listItems.add('Entire Sketch', True, 'Resources/Sketch')
else:
_typeList.listItems.add('Entire Sketch', False, 'Resources/Sketch')
if typeState == 'Sketch Profiles':
_typeList.listItems.add('Sketch Profiles', True, 'Resources/Profiles')
else:
_typeList.listItems.add('Sketch Profiles', False, 'Resources/Profiles')
if isAdvanced:
_typeList.isVisible = True
else:
_typeList.isVisible = False
_showOvercuts = inputs.addBoolValueInput('showOvercuts', 'Show Overcuts', True, '', False)
_showOvercuts.isVisible = False
_selectInput = inputs.addSelectionInput('selection', 'Selection', 'Select the entity to export.')
_textNotes = inputs.addTextBoxCommandInput('notes', '', '', 2, True)
_textNotes.isFullWidth = False
_textNotes.isVisible = True
if isAdvanced:
_advanced = inputs.addBoolValueInput('Advanced', 'Advanced', True, '', True)
else:
_advanced = inputs.addBoolValueInput('Advanced', 'Advanced', True, '', False)
_advanced.isVisible = True
_shaperLink = inputs.addTextBoxCommandInput('fullWidth_textBox', '', _shaperLinkText, 1, True)
_shaperLink.isFullWidth = True
_shaperLink.isReadOnly = True
versionText = '<div align="right">Version ' + _version + '</div>'
_versionText = inputs.addTextBoxCommandInput('version_textBox', '', versionText, 1, True)
# Connect to the command related events.
onExecute = ExportCommandExecuteHandler()
cmd.execute.add(onExecute)
_handlers.append(onExecute)
onInputChanged = ExportCommandInputChangedHandler()
cmd.inputChanged.add(onInputChanged)
_handlers.append(onInputChanged)
onSelectionEvent = ExportCommandSelectionEventHandler()
cmd.selectionEvent.add(onSelectionEvent)
_handlers.append(onSelectionEvent)
onActivate = ExportCommandActivateHandler()
cmd.activate.add(onActivate)
_handlers.append(onActivate)
_firstActivate = True
except:
if _logFile:
_logFile.write('Failed in ExportCommandCreatedEventHandler')
_logFile.write('Failed:\n{}'.format(traceback.format_exc()))
_logFile.flush()
if _ui:
_ui.messageBox('An unexpected error occurred in the Shaper Origin Export command.' +
'\n\nAn error log is available in:\n"' + _logFile.name + '".', 'Shaper Origin Export',
adsk.core.MessageBoxButtonTypes.OKButtonType, adsk.core.MessageBoxIconTypes.CriticalIconType)
# Event handler for the selectionEvent event.
class ExportCommandSelectionEventHandler(adsk.core.SelectionEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
try:
eventArgs = adsk.core.SelectionEventArgs.cast(args)
cmd = eventArgs.firingEvent.sender
typeList = adsk.core.DropDownCommandInput.cast(cmd.commandInputs.itemById('typeList'))
selInput = eventArgs.activeInput
if typeList.selectedItem.name == 'Single Solid Body':
# Get the selected face.
fc = adsk.fusion.BRepFace.cast(eventArgs.selection.entity)
# Get the parent body.
body = fc.body
# Make sure the body is solid.
if not body.isSolid:
eventArgs.isSelectable = False
return
# Highlight the parent body using custom graphics.
# if fc:
# # Draw the body using custom graphics to show it as being highlighted.
# des = adsk.fusion.Design.cast(_app.activeProduct)
# root = des.rootComponent
#
# for group in root.customGraphicsGroups:
# if group.id == 'BodySelect':
# group.deleteMe()
#
# group = root.customGraphicsGroups.add()
# group.id = 'BodySelect'
# graphicsBody = group.addBRepBody(fc.body)
# highlightColor = adsk.core.Color.create(200, 200, 200, 150)
# graphicsBody.color = adsk.fusion.CustomGraphicsSolidColorEffect.create(highlightColor)
# _app.activeViewport.refresh()
# else:
# eventArgs.isSelectable = False
elif typeList.selectedItem.name == 'Faces':
if selInput.selectionCount > 0:
# Check the the plane are parallel.
existingFace = adsk.fusion.BRepFace.cast(selInput.selection(0).entity)
selectedFace = adsk.fusion.BRepFace.cast(eventArgs.selection.entity)
if existingFace.body == selectedFace.body:
(retVal, normal) = existingFace.evaluator.getNormalAtPoint(existingFace.pointOnFace)
(retVal, selNormal) = selectedFace.evaluator.getNormalAtPoint(selectedFace.pointOnFace)
if selNormal.isParallelTo(normal):
eventArgs.isSelectable = True
else:
eventArgs.isSelectable = False
else:
eventArgs.isSelectable = False
else:
eventArgs.isSelectable = True
elif typeList.selectedItem.name == 'Entire Sketch':
if isinstance(eventArgs.selection.entity, adsk.fusion.SketchCurve):
skCurve = adsk.fusion.SketchCurve.cast(eventArgs.selection.entity)
moreEnts = adsk.core.ObjectCollection.create()
moreEnts.add(skCurve.parentSketch)
eventArgs.additionalEntities = moreEnts
elif isinstance(eventArgs.selection.entity, adsk.fusion.Profile):
prof = adsk.fusion.Profile.cast(eventArgs.selection.entity)
moreEnts = adsk.core.ObjectCollection.create()
moreEnts.add(prof.parentSketch)
eventArgs.additionalEntities = moreEnts
elif isinstance(eventArgs.selection.entity, adsk.fusion.SketchText):
skText = adsk.fusion.SketchText.cast(eventArgs.selection.entity)
moreEnts = adsk.core.ObjectCollection.create()
moreEnts.add(skText.parentSketch)
eventArgs.additionalEntities = moreEnts
elif typeList.selectedItem.name =='Sketch Profiles':
if selInput.selectionCount > 0:
existingProfile = adsk.fusion.Profile.cast(selInput.selection(0).entity)
if existingProfile.parentSketch == eventArgs.selection.entity.parentSketch:
eventArgs.isSelectable = True
else:
eventArgs.isSelectable = False
else:
eventArgs.isSelectable = True
elif typeList.selectedItem.name == 'Solid Bodies Layout':
# Get the selected body.
body = adsk.fusion.BRepBody.cast(eventArgs.selection.entity)
# Determine if the selected body is oriented relative to the X-Y plane.
if not isOrientedOnXYPlane(body):
eventArgs.isSelectable = False
return
else:
_ui.messageBox('test')
except:
if _logFile:
_logFile.write('Failed in ExportCommandSelectionEventHandler')
_logFile.write('Failed:\n{}'.format(traceback.format_exc()))
_logFile.flush()
if _ui:
_ui.messageBox('An unexpected error occurred in the Shaper Origin Export command.' +
'\n\nAn error log is available in:\n"' + _logFile.name + '".', 'Shaper Origin Export',
adsk.core.MessageBoxButtonTypes.OKButtonType, adsk.core.MessageBoxIconTypes.CriticalIconType)
# Event handler for the activate event.
class ExportCommandActivateHandler(adsk.core.CommandEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
global _firstActivate
try:
if _firstActivate:
eventArgs = adsk.core.CommandEventArgs.cast(args)
# Activate the visible selection.
# listItem = adsk.core.ListItem.cast(None)
# listItem = _typeList.listItems.item(0)
# listItem.isSelected = True
setSelectionState(_typeList.selectedItem.name)
_firstActivate = False
except:
if _logFile:
_logFile.write('Failed in ExportCommandActivateHandler')
_logFile.write('Failed:\n{}'.format(traceback.format_exc()))
_logFile.flush()
if _ui:
_ui.messageBox('An unexpected error occurred in the Shaper Origin Export command.' +
'\n\nAn error log is available in:\n"' + _logFile.name + '".', 'Shaper Origin Export',
adsk.core.MessageBoxButtonTypes.OKButtonType, adsk.core.MessageBoxIconTypes.CriticalIconType)
# Event handler for the inputChanged event.
class ExportCommandInputChangedHandler(adsk.core.InputChangedEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
try:
eventArgs = adsk.core.InputChangedEventArgs.cast(args)
if eventArgs.input.id == 'typeList':
selItem = _typeList.selectedItem.name
setSelectionState(selItem)
elif eventArgs.input.id == 'Advanced':
if _advanced.value:
_typeList.isVisible = True
else:
_typeList.listItems.item(0).isSelected = True
setSelectionState('Single Solid Body')
_typeList.isVisible = False
_showOvercuts.isVisible = False
except:
if _logFile:
_logFile.write('Failed in ExportCommandInputChangedHandler')
_logFile.write('Failed:\n{}'.format(traceback.format_exc()))
_logFile.flush()
if _ui:
_ui.messageBox('An unexpected error occurred in the Shaper Origin Export command.' +
'\n\nAn error log is available in:\n"' + _logFile.name + '".', 'Shaper Origin Export',
adsk.core.MessageBoxButtonTypes.OKButtonType, adsk.core.MessageBoxIconTypes.CriticalIconType)
def setSelectionState(stateName):
try:
# if _isBeta and not _advanced.value:
# _showOvercuts.isVisible = False
_selectInput.clearSelection()
_selectInput.clearSelectionFilter()
if stateName == 'Single Solid Body':
_selectInput.addSelectionFilter('PlanarFaces')
_selectInput.setSelectionLimits(1,1)
_textNotes.formattedText = _solidBodyPrompt
_textNotes.numRows = 4
# if _isBeta:
# _showOvercuts.isVisible = True
elif stateName =='Faces':
_selectInput.addSelectionFilter('PlanarFaces')
_selectInput.setSelectionLimits(1,0)
_textNotes.formattedText = _facePrompt
_textNotes.numRows = 4
elif stateName =='Entire Sketch':
_selectInput.addSelectionFilter('Sketches')
_selectInput.addSelectionFilter('SketchCurves')
_selectInput.addSelectionFilter('Texts')
_selectInput.setSelectionLimits(1,1)
_textNotes.formattedText = _sketchPrompt
_textNotes.numRows = 3
elif stateName =='Sketch Profiles':
_selectInput.addSelectionFilter('Profiles')
_selectInput.setSelectionLimits(1,0)
_textNotes.formattedText = _profilePrompt
_textNotes.numRows = 4
elif stateName =='Solid Bodies Layout':
_selectInput.addSelectionFilter('SolidBodies')
_selectInput.setSelectionLimits(1,0)
_textNotes.formattedText = _layoutsPrompt
# if _isBeta:
# _showOvercuts.isVisible = True
except:
if _logFile:
_logFile.write('Failed in setSelectionState')
_logFile.write('Failed:\n{}'.format(traceback.format_exc()))
_logFile.flush()
if _ui:
_ui.messageBox('An unexpected error occurred in the Shaper Origin Export command.' +
'\n\nAn error log is available in:\n"' + _logFile.name + '".', 'Shaper Origin Export',
adsk.core.MessageBoxButtonTypes.OKButtonType, adsk.core.MessageBoxIconTypes.CriticalIconType)
# Event handler for the execute event.
class ExportCommandExecuteHandler(adsk.core.CommandEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
orientedBody = None
try:
eventArgs = adsk.core.CommandEventArgs.cast(args)
inputs = eventArgs.command.commandInputs
# Save the current settings to use as the default.
with open(os.path.join(_appPath, 'defaults.dat'), 'w') as file:
advancedState = str(_advanced.value)
typeState = _typeList.selectedItem.name
file.write('advanced:' + advancedState + ';type:' + typeState + ';beta:' + str(_isBeta) )
des = adsk.fusion.Design.cast(_app.activeProduct)
root = des.rootComponent
# Get the inputs.
showOverCuts = _showOvercuts.value
filename = ''
selItem = _typeList.selectedItem.name
if selItem == 'Single Solid Body':
selection = _selectInput.selection(0)
selectedFace = adsk.fusion.BRepFace.cast(selection.entity)
body = selectedFace.body
filename = body.name
# Get a copy of the original body that is oriented flat onto
# the X-Y plane and create a Board object from it.
(newOcc, orientedBody) = getOrientedBody(root, selectedFace)
newBoard = MyBoard(orientedBody)
newOcc.deleteMe()
if not newBoard:
return
allComposites = [newBoard.perimeterComposite]
if newBoard.pocketComposites:
allComposites += newBoard.pocketComposites
if newBoard.internalThroughCuts:
allComposites += newBoard.internalThroughCuts
compSets = []
totalRange = None
for comp in allComposites:
if not totalRange:
totalRange = comp.rangeBox
else:
totalRange.combine(comp.rangeBox)
compSets.append(allComposites)
svgData = getSVG(allComposites, 'Title', 'Description', _version, totalRange, newBoard.thickness, showOverCuts)
elif selItem =='Solid Bodies Layout':
des = adsk.fusion.Design.cast(_app.activeProduct)
bodies = []
for i in range(_selectInput.selectionCount):
bodies.append(_selectInput.selection(i).entity)
# Check to see if there is a pending snapshot and capture it if there is.
if des.designType == adsk.fusion.DesignTypes.ParametricDesignType:
if des.snapshots.hasPendingSnapshot:
des.snapshots.add()
# Check to see if any of the bodies come from a referenced component.
refOccs = []
currentDoc = _app.activeDocument
for body in bodies:
parentComp = body.parentComponent
parentDes = parentComp.parentDesign
parentDoc = parentDes.parentDocument
if parentDoc.name != currentDoc.name:
root = des.rootComponent
occs = root.allOccurrencesByComponent(parentDes.rootComponent)
if occs.count > 0:
refOccs.append(occs.item(0))
if len(refOccs) > 0:
_ui.messageBox('There is a current limitation that referenced components cannot be used. You must first break the link on the linked component in the browser and then run the "Export to Origin" command.\n\nYou can undo the break link after you have exported the SVG to maintain the link.')
eventArgs.executeFailed = True
return
# refOcc = adsk.fusion.Occurrence.cast(None)
# for refOcc in refOccs:
# if refOcc.isReferencedComponent:
# refOcc.breakLink()
# Construct the filename.
filename = _app.activeProduct.rootComponent.name
if not (filename[:1] == '(' and filename[len(filename)-1:len(filename)] == ')'):
filename = filename[:filename.rfind(' v')]
filename = filename + '_layout'
boardsCompositeSets = []
boardThicknesses = []
boardNames = []
compSets = []
totalRange = None
for body in bodies:
newBoard = MyBoard(body)
boardNames.append(body.name)
boardThicknesses.append(newBoard.thickness)
# Initialize the list with the perimeter composite.
boardComposites = [newBoard.perimeterComposite]
# Add the pockets, if there are any.
if newBoard.pocketComposites:
boardComposites += newBoard.pocketComposites
# Add the internal cutouts, if there are any.
if newBoard.internalThroughCuts:
boardComposites += newBoard.internalThroughCuts
boardsCompositeSets.append(boardComposites)
for boardList in boardsCompositeSets:
for comp in boardList:
if not totalRange:
totalRange = comp.rangeBox
else:
totalRange.combine(comp.rangeBox)
svgData = getLayoutSVG(boardsCompositeSets, 'Title', 'Description', _version, totalRange, boardThicknesses, boardNames, showOverCuts)
elif selItem =='Faces':
faces = []
for i in range(_selectInput.selectionCount):
faces.append(_selectInput.selection(i).entity)
body = faces[0].body
(newOcc, orientedBody) = getOrientedBody(root, faces[0])
filename = body.name + '_Faces'
faceIndices = []
indexCount = 0
for testFace in body.faces:
for face in faces:
if face == testFace:
faceIndices.append(indexCount)
indexCount += 1
faces.clear()
for faceIndex in faceIndices:
faces.append(orientedBody.faces.item(faceIndex))
composites = createCompositesFromFaces(faces)
newOcc.deleteMe()
totalRange = None
for comp in composites:
if not totalRange:
totalRange = comp.rangeBox
else:
totalRange.combine(comp.rangeBox)
svgData = getSVG([composites], 'Title', 'Description', _version, totalRange, 0, False)
elif selItem =='Entire Sketch':
for i in range(_selectInput.selectionCount):
if isinstance(_selectInput.selection(i).entity, adsk.fusion.Sketch):
sk = _selectInput.selection(i).entity
filename = sk.name
composites = createCompositesFromSketch(sk, True)
totalRange = None
for comp in composites:
if not totalRange:
totalRange = comp.rangeBox
else:
totalRange.combine(comp.rangeBox)
svgData = getSVG([composites], 'Title', 'Description', _version, totalRange, 0, False)
elif selItem =='Sketch Profiles':
if True:
# Original code that creates output for selected profiles.
profiles = []
for i in range(_selectInput.selectionCount):
profiles.append(_selectInput.selection(i).entity)
filename = profiles[0].parentSketch.name + '_Profiles'
composites = createCompositesFromProfiles(profiles)
else:
# Code that finds all profiles whose name begins with "Cut"
# and uses them. This was hacked together for the star.
selProf = adsk.fusion.Profile.cast(_selectInput.selection(0).entity)
profiles = []
sk = adsk.fusion.Sketch.cast(None)
for sk in root.sketches:
if sk.name.startswith('Cut'):
prof = sk.profiles.item(0)
profiles.append(prof)
filename = 'CutSketches_Profiles'
composites = createCompositesFromProfiles(profiles, True)
totalRange = None
for comp in composites:
if not totalRange:
totalRange = comp.rangeBox
else:
totalRange.combine(comp.rangeBox)
svgData = getSVG([composites], 'Title', 'Description', _version, totalRange, 0, False)
# if newOcc:
# newOcc.deleteMe()
fileDialog = _ui.createFileDialog()
fileDialog.isMultiSelectEnabled = False
fileDialog.title = "Specify output filename"
fileDialog.filter = 'SVG files (*.svg)'
fileDialog.filterIndex = 0
fileDialog.initialFilename = filename + '.svg'
dialogResult = fileDialog.showSave()
if dialogResult == adsk.core.DialogResults.DialogOK:
filename = fileDialog.filename
else:
eventArgs.executeFailed = True
return
with open(filename, 'w') as text_file:
text_file.write(svgData)
eventArgs.executeFailed = True
except:
eventArgs.executeFailed = True
if orientedBody:
orientedBody.isVisible = False
if _logFile:
_logFile.write('Failed in ExportCommandExecuteHandler')
_logFile.write('Failed:\n{}'.format(traceback.format_exc()))
_logFile.flush()
if _ui:
_ui.messageBox('An unexpected error occurred while executing the Shaper Origin Export command.' +
'\n\nAn error log is available in:\n"' + _logFile.name + '".', 'Shaper Origin Export',
adsk.core.MessageBoxButtonTypes.OKButtonType, adsk.core.MessageBoxIconTypes.CriticalIconType)
# Determines if the input body is oriented so it is lying on the X-Y plane.
def isOrientedOnXYPlane(body):
try:
# As a first check, verify that the X-Y size of the bounding
# box is larger than the Z size.
b = adsk.fusion.BRepBody.cast(body)
xSize = b.boundingBox.maxPoint.x - b.boundingBox.minPoint.x
ySize = b.boundingBox.maxPoint.y - b.boundingBox.minPoint.y
zSize = b.boundingBox.maxPoint.z - b.boundingBox.minPoint.z
xyDiag = math.sqrt(math.pow(xSize,2) + math.pow(ySize,2))
if xyDiag < zSize:
return False
# Find the planar face with the largest area.
maxArea = 0
maxFace = None
face = adsk.fusion.BRepFace.cast(None)
for face in b.faces:
if face.geometry.surfaceType == adsk.core.SurfaceTypes.PlaneSurfaceType:
if face.area > maxArea:
maxArea = face.area
maxFace = face
# Assuming the found face is either the top or bottom face, get
# it's normal and verify that it is parallel to the Z axis.
if maxFace:
(retVal, normal) = maxFace.evaluator.getNormalAtPoint(maxFace.pointOnFace)
if normal.isParallelTo(adsk.core.Vector3D.create(0,0,1)):
return True
else:
return False
else:
return False
except:
if _logFile:
_logFile.write('Failed in isOrientedOnXYPlane')
_logFile.write('Failed:\n{}'.format(traceback.format_exc()))
_logFile.flush()
raise
def getOrientedBody(root, upperFace):
try:
# Get the body from the input face.
originalBody = upperFace.body
# Create a new occurrence (and component).
trans = adsk.core.Matrix3D.create()
occ = adsk.fusion.Occurrence.cast(root.occurrences.addNewComponent(trans))
# Copy the body into the new component.
body = originalBody.copyToComponent(occ)
# Use the normal of the upper face as the Z direction.
zVec = adsk.core.Vector3D.cast(None)
(retVal, zVec) = upperFace.evaluator.getNormalAtPoint(upperFace.pointOnFace)
# Find the longest linear edge of the body that is more than 45 degrees from the "up" direction.
maxLength = 0
maxEdge = None
edge = adsk.fusion.BRepEdge.cast(None)
for edge in body.edges:
if edge.geometry:
if edge.geometry.objectType == adsk.core.Line3D.classType():
lineGeom = adsk.core.Line3D.cast(edge.geometry)
lineVec = lineGeom.startPoint.vectorTo(lineGeom.endPoint)
lineAngle = lineVec.angleTo(zVec)
if lineAngle > (math.pi * 0.25) and lineAngle < (math.pi * 0.75):
if edge.length > maxLength:
maxLength = edge.length
maxEdge = edge
if maxEdge:
# Check length of edge relative to the bounding box size along the major axes.
# If the edge is less than 30% of a major axis, then use which ever major axis
# that is perpendicular to the upper face normal that is the longest.
boundX = body.boundingBox.maxPoint.x - body.boundingBox.minPoint.x
boundY = body.boundingBox.maxPoint.y - body.boundingBox.minPoint.y
boundZ = body.boundingBox.maxPoint.z - body.boundingBox.minPoint.z
# Initialize the values assuming the X direction is closest to the normal direction.
startPnt = body.boundingBox.minPoint.copy()
normalAngle = adsk.core.Vector3D.create(1,0,0).angleTo(zVec)
if boundY > boundZ:
xVec = adsk.core.Vector3D.create(0,1,0)
boundMax = boundY
endPnt = startPnt.copy()
endPnt.y += boundMax
else:
xVec = adsk.core.Vector3D.create(0,0,1)
boundMax = boundZ
endPnt = startPnt.copy()
endPnt.z += boundMax
# Check to see of the Y direction is closest to the face normal.
testAngle = adsk.core.Vector3D.create(0,1,0).angleTo(zVec)
if testAngle < normalAngle:
normalAngle = testAngle
if boundX > boundZ:
xVec = adsk.core.Vector3D.create(1,0,0)
boundMax = boundX
endPnt = startPnt.copy()
endPnt.x += boundMax
else:
xVec = adsk.core.Vector3D.create(0,0,1)
boundMax = boundZ
endPnt = startPnt.copy()
endPnt.z += boundMax
# Check to see of the Z direction is closest to the face normal.
testAngle = adsk.core.Vector3D.create(0,0,1).angleTo(zVec)
if testAngle < normalAngle:
normalAngle = testAngle
if boundX > boundY:
xVec = adsk.core.Vector3D.create(1,0,0)
boundMax = boundX
endPnt = startPnt.copy()
endPnt.x += boundMax
else:
xVec = adsk.core.Vector3D.create(0,1,0)
boundMax = boundY
endPnt = startPnt.copy()
endPnt.y += boundMax
# Check to see if the longest edge is greater than 30% of the longest bounds.
if maxLength < boundMax * 0.3:
# Use the bounding box direction.
xVec.normalize()
yVec = zVec.crossProduct(xVec)
startPnt = maxEdge.startVertex.geometry
endPoint = maxEdge.endVertex.geometry
else:
# Use the found edge.
xVec = maxEdge.startVertex.geometry.vectorTo(maxEdge.endVertex.geometry)
xVec.normalize()
yVec = zVec.crossProduct(xVec)
startPnt = maxEdge.startVertex.geometry
endPoint = maxEdge.endVertex.geometry
midPoint = adsk.core.Point3D.create((startPnt.x + endPoint.x)/2, (startPnt.y + endPoint.y)/2, (startPnt.z + endPoint.z)/2)
testVec = midPoint.vectorTo(upperFace.pointOnFace)
origin = maxEdge.startVertex.geometry
if yVec.angleTo(testVec) > math.pi/2:
xVec.scaleBy(-1)
yVec = zVec.crossProduct(xVec)
origin = maxEdge.endVertex.geometry
yVec.normalize()
else:
# There aren't any linear edges so create a coordinate system that uses the
# Z axis of the upper face and finds the model axes that most closely align.
if zVec.angleTo(adsk.core.Vector3D.create(1,0,0)) <= math.pi * 0.25:
xVec = adsk.core.Vector3D.create(0,1,0)
yVec = adsk.core.Vector3D.create(0,0,1)
elif zVec.angleTo(adsk.core.Vector3D.create(-1,0,0)) <= math.pi * 0.25:
xVec = adsk.core.Vector3D.create(0,1,0)
yVec = adsk.core.Vector3D.create(0,0,1)
elif zVec.angleTo(adsk.core.Vector3D.create(0,1,0)) <= math.pi * 0.25:
xVec = adsk.core.Vector3D.create(-1,0,0)
yVec = adsk.core.Vector3D.create(0,0,1)
elif zVec.angleTo(adsk.core.Vector3D.create(0,-1,0)) <= math.pi * 0.25:
xVec = adsk.core.Vector3D.create(1,0,0)
yVec = adsk.core.Vector3D.create(0,0,1)
elif zVec.angleTo(adsk.core.Vector3D.create(0,0,1)) <= math.pi * 0.25:
xVec = adsk.core.Vector3D.create(1,0,0)
yVec = adsk.core.Vector3D.create(0,1,0)
elif zVec.angleTo(adsk.core.Vector3D.create(0,0,-1)) <= math.pi * 0.25:
xVec = adsk.core.Vector3D.create(-1,0,0)
yVec = adsk.core.Vector3D.create(0,1,0)
yVec = zVec.crossProduct(xVec)
xVec = yVec.crossProduct(zVec)
origin = upperFace.pointOnFace
trans.setWithCoordinateSystem(origin, xVec, yVec, zVec)
trans.invert()
currentTrans = occ.transform
currentTrans.transformBy(trans)
occ.transform = currentTrans
des = adsk.fusion.Design.cast(root.parentDesign)
if des.designType == adsk.fusion.DesignTypes.ParametricDesignType:
if des.snapshots.hasPendingSnapshot:
des.snapshots.add()
return [occ, body]
except:
if _logFile:
_logFile.write('Failed in getOrientedBody')
_logFile.write('Failed:\n{}'.format(traceback.format_exc()))
_logFile.flush()
raise
def run(context):
try:
global _app, _ui
global _logFile
_app = adsk.core.Application.get()
_ui = _app.userInterface
try:
tempDir = tempfile.gettempdir()
filename = os.path.join(tempDir, 'ShaperUtilities.log')
_logFile = open(filename, 'w')
setLogFile(_logFile)
except:
_logFile = None
# Create the command to export SVG.
exportCmdDef = _ui.commandDefinitions.addButtonDefinition('shaperExport', 'Export to Origin', 'Export the selected body(s) to a file that can be used by Shaper Origin.', 'resources/shaperExport')
# Connect to the command created event.
exportCommandCreated = ExportCommandCreatedEventHandler()
exportCmdDef.commandCreated.add(exportCommandCreated)
_handlers.append(exportCommandCreated)
# Get the MODEL workspace.
modelWS = _ui.workspaces.itemById('FusionSolidEnvironment')
# Add a new SHAPER panel.
shaperPanel = modelWS.toolbarPanels.add('shaperPanel', 'SHAPER', 'SolidScriptsAddinsPanel', False)
# Add the buttons to the panel.
exportCntrl = shaperPanel.controls.addCommand(exportCmdDef)
exportCntrl.isPromotedByDefault = True
exportCntrl.isPromoted = True
except:
if _logFile:
_logFile.write('Failed:\n{}'.format(traceback.format_exc()))
_logFile.flush()
if _ui:
_ui.messageBox('An unexpected error occurred while loading the Shaper Utilities add-in.'
'\n\nAn error log is available in:\n"' + _logFile.name + '".', 'Shaper Origin Export',
adsk.core.MessageBoxButtonTypes.OKButtonType, adsk.core.MessageBoxIconTypes.CriticalIconType)
def stop(context):
try:
panel = _ui.allToolbarPanels.itemById('shaperPanel')
if panel:
panel.deleteMe()
exportCmdDef = _ui.commandDefinitions.itemById('shaperExport')
if exportCmdDef:
exportCmdDef.deleteMe()
except:
if _logFile:
_logFile.write('Failed:\n{}'.format(traceback.format_exc()))
_logFile.flush()
if _ui:
_ui.messageBox('An unexpected error occurred while unloading the Shaper Utilities add-in.'
'\n\nAn error log is available in:\n"' + _logFile.name + '".', 'Shaper Origin Export',
adsk.core.MessageBoxButtonTypes.OKButtonType, adsk.core.MessageBoxIconTypes.CriticalIconType)
Tested under Windows 7.