sdk-py
17
python/AddInSamples/AddInSample/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python: Attach",
|
||||||
|
"type": "python",
|
||||||
|
"request": "attach",
|
||||||
|
"pathMappings": [ {
|
||||||
|
"localRoot": "${workspaceRoot}",
|
||||||
|
"remoteRoot": "${workspaceRoot}"}],
|
||||||
|
"osx": {"filePath":"${file}"},
|
||||||
|
"windows": {"filePath":"${file}"},
|
||||||
|
"port": 9000,
|
||||||
|
"host": "localhost"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
14
python/AddInSamples/AddInSample/AddInSample.manifest
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"autodeskProduct":"Fusion360",
|
||||||
|
"type":"addin",
|
||||||
|
"id":"319679C5-AF6D-4A46-862D-93414F1AE117",
|
||||||
|
"author":"Autodesk Inc.",
|
||||||
|
"description":{
|
||||||
|
"": "This is sample addin.",
|
||||||
|
"1033": "This is sample addin.",
|
||||||
|
"2052": "这是一个附加模块的例子。"
|
||||||
|
},
|
||||||
|
"version":"0.0.1",
|
||||||
|
"runOnStartup":false,
|
||||||
|
"supportedOS":"windows|mac"
|
||||||
|
}
|
||||||
314
python/AddInSamples/AddInSample/AddInSample.py
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
#Author-Autodesk Inc.
|
||||||
|
#Description-This is sample addin.
|
||||||
|
|
||||||
|
import adsk.core, adsk.fusion, traceback, os, gettext
|
||||||
|
|
||||||
|
btnCmdIdOnQAT = 'demoButtonCommandOnQAT'
|
||||||
|
listCmdIdOnQAT = 'demoListCommandOnQAT'
|
||||||
|
commandIdOnPanel = 'demoCommandOnPanel'
|
||||||
|
selectionInputId = 'selectionInput'
|
||||||
|
distanceInputId = 'distanceValueCommandInput'
|
||||||
|
panelId = 'SolidCreatePanel'
|
||||||
|
|
||||||
|
# global set of event handlers to keep them referenced for the duration of the command
|
||||||
|
handlers = []
|
||||||
|
|
||||||
|
# Support localization
|
||||||
|
_ = None
|
||||||
|
def getUserLanguage():
|
||||||
|
app = adsk.core.Application.get()
|
||||||
|
|
||||||
|
return {
|
||||||
|
adsk.core.UserLanguages.ChinesePRCLanguage: "zh-CN",
|
||||||
|
adsk.core.UserLanguages.ChineseTaiwanLanguage: "zh-TW",
|
||||||
|
adsk.core.UserLanguages.CzechLanguage: "cs-CZ",
|
||||||
|
adsk.core.UserLanguages.EnglishLanguage: "en-US",
|
||||||
|
adsk.core.UserLanguages.FrenchLanguage: "fr-FR",
|
||||||
|
adsk.core.UserLanguages.GermanLanguage: "de-DE",
|
||||||
|
adsk.core.UserLanguages.HungarianLanguage: "hu-HU",
|
||||||
|
adsk.core.UserLanguages.ItalianLanguage: "it-IT",
|
||||||
|
adsk.core.UserLanguages.JapaneseLanguage: "ja-JP",
|
||||||
|
adsk.core.UserLanguages.KoreanLanguage: "ko-KR",
|
||||||
|
adsk.core.UserLanguages.PolishLanguage: "pl-PL",
|
||||||
|
adsk.core.UserLanguages.PortugueseBrazilianLanguage: "pt-BR",
|
||||||
|
adsk.core.UserLanguages.RussianLanguage: "ru-RU",
|
||||||
|
adsk.core.UserLanguages.SpanishLanguage: "es-ES"
|
||||||
|
}[app.preferences.generalPreferences.userLanguage]
|
||||||
|
|
||||||
|
# Get loc string by language
|
||||||
|
def getLocStrings():
|
||||||
|
currentDir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
return gettext.translation('resource', currentDir, [getUserLanguage(), "en-US"]).gettext
|
||||||
|
|
||||||
|
def commandDefinitionById(id):
|
||||||
|
app = adsk.core.Application.get()
|
||||||
|
ui = app.userInterface
|
||||||
|
if not id:
|
||||||
|
ui.messageBox(_('commandDefinition id is not specified'))
|
||||||
|
return None
|
||||||
|
commandDefinitions_ = ui.commandDefinitions
|
||||||
|
commandDefinition_ = commandDefinitions_.itemById(id)
|
||||||
|
return commandDefinition_
|
||||||
|
|
||||||
|
def commandControlByIdForQAT(id):
|
||||||
|
app = adsk.core.Application.get()
|
||||||
|
ui = app.userInterface
|
||||||
|
if not id:
|
||||||
|
ui.messageBox(_('commandControl id is not specified'))
|
||||||
|
return None
|
||||||
|
toolbars_ = ui.toolbars
|
||||||
|
toolbarQAT_ = toolbars_.itemById('QAT')
|
||||||
|
toolbarControls_ = toolbarQAT_.controls
|
||||||
|
toolbarControl_ = toolbarControls_.itemById(id)
|
||||||
|
return toolbarControl_
|
||||||
|
|
||||||
|
def commandControlByIdForPanel(id):
|
||||||
|
app = adsk.core.Application.get()
|
||||||
|
ui = app.userInterface
|
||||||
|
if not id:
|
||||||
|
ui.messageBox(_('commandControl id is not specified'))
|
||||||
|
return None
|
||||||
|
workspaces_ = ui.workspaces
|
||||||
|
modelingWorkspace_ = workspaces_.itemById('FusionSolidEnvironment')
|
||||||
|
toolbarPanels_ = modelingWorkspace_.toolbarPanels
|
||||||
|
toolbarPanel_ = toolbarPanels_.itemById(panelId)
|
||||||
|
toolbarControls_ = toolbarPanel_.controls
|
||||||
|
toolbarControl_ = toolbarControls_.itemById(id)
|
||||||
|
return toolbarControl_
|
||||||
|
|
||||||
|
def destroyObject(uiObj, tobeDeleteObj):
|
||||||
|
if uiObj and tobeDeleteObj:
|
||||||
|
if tobeDeleteObj.isValid:
|
||||||
|
tobeDeleteObj.deleteMe()
|
||||||
|
else:
|
||||||
|
uiObj.messageBox(_('tobeDeleteObj is not a valid object'))
|
||||||
|
|
||||||
|
def run(context):
|
||||||
|
ui = None
|
||||||
|
try:
|
||||||
|
app = adsk.core.Application.get()
|
||||||
|
ui = app.userInterface
|
||||||
|
|
||||||
|
global _
|
||||||
|
_ = getLocStrings()
|
||||||
|
|
||||||
|
commandName = _('Demo')
|
||||||
|
commandDescription = _('Demo Command')
|
||||||
|
commandResources = './resources'
|
||||||
|
iconResources = './resources'
|
||||||
|
|
||||||
|
class InputChangedHandler(adsk.core.InputChangedEventHandler):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
def notify(self, args):
|
||||||
|
try:
|
||||||
|
command = args.firingEvent.sender
|
||||||
|
cmdInput = args.input
|
||||||
|
if cmdInput.id != distanceInputId:
|
||||||
|
ui.messageBox(_('Input: {} changed event triggered').format(command.parentCommandDefinition.id))
|
||||||
|
|
||||||
|
if cmdInput.id == selectionInputId:
|
||||||
|
inputs = cmdInput.commandInputs
|
||||||
|
distanceInput = inputs.itemById(distanceInputId)
|
||||||
|
|
||||||
|
if cmdInput.selectionCount > 0:
|
||||||
|
sel = cmdInput.selection(0)
|
||||||
|
selPt = sel.point
|
||||||
|
ent = sel.entity
|
||||||
|
plane = ent.geometry
|
||||||
|
|
||||||
|
distanceInput.setManipulator(selPt, plane.normal)
|
||||||
|
distanceInput.expression = "10mm * 2"
|
||||||
|
distanceInput.isEnabled = True
|
||||||
|
distanceInput.isVisible = True
|
||||||
|
else:
|
||||||
|
distanceInput.isEnabled = False
|
||||||
|
distanceInput.isVisible = False
|
||||||
|
except:
|
||||||
|
if ui:
|
||||||
|
ui.messageBox(_('Input changed event failed: {}').format(traceback.format_exc()))
|
||||||
|
|
||||||
|
class CommandExecuteHandler(adsk.core.CommandEventHandler):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
def notify(self, args):
|
||||||
|
try:
|
||||||
|
command = args.firingEvent.sender
|
||||||
|
ui.messageBox(_('command: {} executed successfully').format(command.parentCommandDefinition.id))
|
||||||
|
except:
|
||||||
|
if ui:
|
||||||
|
ui.messageBox(_('command executed failed: {}').format(traceback.format_exc()))
|
||||||
|
|
||||||
|
class CommandCreatedEventHandlerPanel(adsk.core.CommandCreatedEventHandler):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
def notify(self, args):
|
||||||
|
try:
|
||||||
|
cmd = args.command
|
||||||
|
cmd.helpFile = 'help.html'
|
||||||
|
|
||||||
|
onExecute = CommandExecuteHandler()
|
||||||
|
cmd.execute.add(onExecute)
|
||||||
|
|
||||||
|
onInputChanged = InputChangedHandler()
|
||||||
|
cmd.inputChanged.add(onInputChanged)
|
||||||
|
# keep the handler referenced beyond this function
|
||||||
|
handlers.append(onExecute)
|
||||||
|
handlers.append(onInputChanged)
|
||||||
|
|
||||||
|
commandInputs_ = cmd.commandInputs
|
||||||
|
commandInputs_.addValueInput('valueInput_', _('Value'), 'cm', adsk.core.ValueInput.createByString('0.0 cm'))
|
||||||
|
commandInputs_.addBoolValueInput('boolvalueInput_', _('Bool'), True)
|
||||||
|
commandInputs_.addStringValueInput('stringValueInput_', _('String Value'), _('Default value'))
|
||||||
|
selInput = commandInputs_.addSelectionInput(selectionInputId, _('Selection'), _('Select one'))
|
||||||
|
selInput.addSelectionFilter('PlanarFaces')
|
||||||
|
selInput.addSelectionFilter('ConstructionPlanes')
|
||||||
|
dropDownCommandInput_ = commandInputs_.addDropDownCommandInput('dropdownCommandInput', _('Drop Down'), adsk.core.DropDownStyles.LabeledIconDropDownStyle)
|
||||||
|
dropDownItems_ = dropDownCommandInput_.listItems
|
||||||
|
dropDownItems_.add(_('ListItem 1'), True)
|
||||||
|
dropDownItems_.add(_('ListItem 2'), False)
|
||||||
|
dropDownItems_.add(_('ListItem 3'), False)
|
||||||
|
dropDownCommandInput2_ = commandInputs_.addDropDownCommandInput('dropDownCommandInput2', _('Drop Down2'), adsk.core.DropDownStyles.CheckBoxDropDownStyle)
|
||||||
|
dropDownCommandInputListItems_ = dropDownCommandInput2_.listItems
|
||||||
|
dropDownCommandInputListItems_.add(_('ListItem 1'), True)
|
||||||
|
dropDownCommandInputListItems_.add(_('ListItem 2'), True)
|
||||||
|
dropDownCommandInputListItems_.add(_('ListItem 3'), False)
|
||||||
|
commandInputs_.addFloatSliderCommandInput('floatSliderCommandInput', _('Slider'), 'cm', 0.0, 10.0, True)
|
||||||
|
buttonRowCommandInput_ = commandInputs_.addButtonRowCommandInput('buttonRowCommandInput', _('Button Row'), False)
|
||||||
|
buttonRowCommandInputListItems_ = buttonRowCommandInput_.listItems
|
||||||
|
buttonRowCommandInputListItems_.add(_('ListItem 1'), False, iconResources)
|
||||||
|
buttonRowCommandInputListItems_.add(_('ListItem 2'), True, iconResources)
|
||||||
|
buttonRowCommandInputListItems_.add(_('ListItem 3'), False, iconResources)
|
||||||
|
|
||||||
|
distanceInput = commandInputs_.addDistanceValueCommandInput(distanceInputId, _('Distance'), adsk.core.ValueInput.createByReal(0.0))
|
||||||
|
distanceInput.isEnabled = False
|
||||||
|
distanceInput.isVisible = False
|
||||||
|
distanceInput.minimumValue = 1.0
|
||||||
|
distanceInput.maximumValue = 10.0
|
||||||
|
|
||||||
|
directionInput = commandInputs_.addDirectionCommandInput('directionInput', _('Direction'))
|
||||||
|
directionInput.setManipulator(adsk.core.Point3D.create(0,0,0), adsk.core.Vector3D.create(1,0,0))
|
||||||
|
directionInput2 = commandInputs_.addDirectionCommandInput('directionInput2', _('Direction2'), iconResources)
|
||||||
|
directionInput2.setManipulator(adsk.core.Point3D.create(0,0,0), adsk.core.Vector3D.create(0,1,0))
|
||||||
|
|
||||||
|
ui.messageBox(_('Panel command created successfully'))
|
||||||
|
except:
|
||||||
|
if ui:
|
||||||
|
ui.messageBox(_('Panel command created failed: {}').format(traceback.format_exc()))
|
||||||
|
|
||||||
|
class CommandCreatedEventHandlerQAT(adsk.core.CommandCreatedEventHandler):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
def notify(self, args):
|
||||||
|
try:
|
||||||
|
command = args.command
|
||||||
|
onExecute = CommandExecuteHandler()
|
||||||
|
command.execute.add(onExecute)
|
||||||
|
# keep the handler referenced beyond this function
|
||||||
|
handlers.append(onExecute)
|
||||||
|
ui.messageBox(_('QAT command created successfully'))
|
||||||
|
except:
|
||||||
|
ui.messageBox(_('QAT command created failed: {}').format(traceback.format_exc()))
|
||||||
|
|
||||||
|
commandDefinitions_ = ui.commandDefinitions
|
||||||
|
|
||||||
|
# add a button command on Quick Access Toolbar
|
||||||
|
toolbars_ = ui.toolbars
|
||||||
|
toolbarQAT_ = toolbars_.itemById('QAT')
|
||||||
|
toolbarControlsQAT_ = toolbarQAT_.controls
|
||||||
|
btnCmdToolbarCtlQAT_ = toolbarControlsQAT_.itemById(btnCmdIdOnQAT)
|
||||||
|
if not btnCmdToolbarCtlQAT_:
|
||||||
|
btnCmdDefinitionQAT_ = commandDefinitions_.itemById(btnCmdIdOnQAT)
|
||||||
|
if not btnCmdDefinitionQAT_:
|
||||||
|
btnCmdDefinitionQAT_ = commandDefinitions_.addButtonDefinition(btnCmdIdOnQAT, commandName, commandDescription, commandResources)
|
||||||
|
onButtonCommandCreated = CommandCreatedEventHandlerQAT()
|
||||||
|
btnCmdDefinitionQAT_.commandCreated.add(onButtonCommandCreated)
|
||||||
|
# keep the handler referenced beyond this function
|
||||||
|
handlers.append(onButtonCommandCreated)
|
||||||
|
btnCmdToolbarCtlQAT_ = toolbarControlsQAT_.addCommand(btnCmdDefinitionQAT_)
|
||||||
|
btnCmdToolbarCtlQAT_.isVisible = True
|
||||||
|
ui.messageBox(_('A demo button command is successfully added to the Quick Access Toolbar'))
|
||||||
|
|
||||||
|
# add a list command on Quick Access Toolbar
|
||||||
|
listCmdToolbarCtlQAT_ = toolbarControlsQAT_.itemById(listCmdIdOnQAT)
|
||||||
|
if not listCmdToolbarCtlQAT_:
|
||||||
|
listCmdDefinitionQAT_ = commandDefinitions_.itemById(listCmdIdOnQAT)
|
||||||
|
if not listCmdDefinitionQAT_:
|
||||||
|
listCmdDefinitionQAT_ = commandDefinitions_.addListDefinition(listCmdIdOnQAT, commandName, adsk.core.ListControlDisplayTypes.CheckBoxListType, commandResources)
|
||||||
|
listItems_ = adsk.core.ListControlDefinition.cast(listCmdDefinitionQAT_.controlDefinition).listItems
|
||||||
|
listItems_.add('Demo item 1', True)
|
||||||
|
listItems_.add('Demo item 2', False)
|
||||||
|
listItems_.add('Demo item 3', False)
|
||||||
|
|
||||||
|
onListCommandCreated = CommandCreatedEventHandlerQAT()
|
||||||
|
listCmdDefinitionQAT_.commandCreated.add(onListCommandCreated)
|
||||||
|
# keep the handler referenced beyond this function
|
||||||
|
handlers.append(onListCommandCreated)
|
||||||
|
listCmdToolbarCtlQAT_ = toolbarControlsQAT_.addCommand(listCmdDefinitionQAT_)
|
||||||
|
listCmdToolbarCtlQAT_.isVisible = True
|
||||||
|
ui.messageBox(_('A demo list command is successfully added to the Quick Access Toolbar'))
|
||||||
|
|
||||||
|
# add a command on create panel in modeling workspace
|
||||||
|
workspaces_ = ui.workspaces
|
||||||
|
modelingWorkspace_ = workspaces_.itemById('FusionSolidEnvironment')
|
||||||
|
toolbarPanels_ = modelingWorkspace_.toolbarPanels
|
||||||
|
toolbarPanel_ = toolbarPanels_.itemById(panelId) # add the new command under the CREATE panel
|
||||||
|
toolbarControlsPanel_ = toolbarPanel_.controls
|
||||||
|
toolbarControlPanel_ = toolbarControlsPanel_.itemById(commandIdOnPanel)
|
||||||
|
if not toolbarControlPanel_:
|
||||||
|
commandDefinitionPanel_ = commandDefinitions_.itemById(commandIdOnPanel)
|
||||||
|
if not commandDefinitionPanel_:
|
||||||
|
commandDefinitionPanel_ = commandDefinitions_.addButtonDefinition(commandIdOnPanel, commandName, commandDescription, commandResources)
|
||||||
|
onCommandCreated = CommandCreatedEventHandlerPanel()
|
||||||
|
commandDefinitionPanel_.commandCreated.add(onCommandCreated)
|
||||||
|
# keep the handler referenced beyond this function
|
||||||
|
handlers.append(onCommandCreated)
|
||||||
|
toolbarControlPanel_ = toolbarControlsPanel_.addCommand(commandDefinitionPanel_)
|
||||||
|
toolbarControlPanel_.isVisible = True
|
||||||
|
ui.messageBox(_('A demo command is successfully added to the create panel in modeling workspace'))
|
||||||
|
|
||||||
|
except:
|
||||||
|
if ui:
|
||||||
|
ui.messageBox(_('AddIn Start Failed: {}').format(traceback.format_exc()))
|
||||||
|
|
||||||
|
def stop(context):
|
||||||
|
ui = None
|
||||||
|
try:
|
||||||
|
app = adsk.core.Application.get()
|
||||||
|
ui = app.userInterface
|
||||||
|
objArrayQAT = []
|
||||||
|
objArrayPanel = []
|
||||||
|
|
||||||
|
btnCmdToolbarCtlQAT_ = commandControlByIdForQAT(btnCmdIdOnQAT)
|
||||||
|
if btnCmdToolbarCtlQAT_:
|
||||||
|
objArrayQAT.append(btnCmdToolbarCtlQAT_)
|
||||||
|
|
||||||
|
btnCmdDefinitionQAT_ = commandDefinitionById(btnCmdIdOnQAT)
|
||||||
|
if btnCmdDefinitionQAT_:
|
||||||
|
objArrayQAT.append(btnCmdDefinitionQAT_)
|
||||||
|
|
||||||
|
listCmdToolbarCtlQAT_ = commandControlByIdForQAT(listCmdIdOnQAT)
|
||||||
|
if listCmdToolbarCtlQAT_:
|
||||||
|
objArrayQAT.append(listCmdToolbarCtlQAT_)
|
||||||
|
|
||||||
|
listCmdDefinitionQAT_ = commandDefinitionById(listCmdIdOnQAT)
|
||||||
|
if listCmdDefinitionQAT_:
|
||||||
|
objArrayQAT.append(listCmdDefinitionQAT_)
|
||||||
|
|
||||||
|
commandControlPanel_ = commandControlByIdForPanel(commandIdOnPanel)
|
||||||
|
if commandControlPanel_:
|
||||||
|
objArrayPanel.append(commandControlPanel_)
|
||||||
|
|
||||||
|
commandDefinitionPanel_ = commandDefinitionById(commandIdOnPanel)
|
||||||
|
if commandDefinitionPanel_:
|
||||||
|
objArrayPanel.append(commandDefinitionPanel_)
|
||||||
|
|
||||||
|
for obj in objArrayQAT:
|
||||||
|
destroyObject(ui, obj)
|
||||||
|
|
||||||
|
for obj in objArrayPanel:
|
||||||
|
destroyObject(ui, obj)
|
||||||
|
|
||||||
|
except:
|
||||||
|
if ui:
|
||||||
|
ui.messageBox(_('AddIn Stop Failed: {}').format(traceback.format_exc()))
|
||||||
BIN
python/AddInSamples/AddInSample/en-US/LC_MESSAGES/resource.mo
Normal file
136
python/AddInSamples/AddInSample/en-US/LC_MESSAGES/resource.po
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2015-08-03 15:59+China Standard Time\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "commandDefinition id is not specified"
|
||||||
|
msgstr "commandDefinition id is not specified"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "commandControl id is not specified"
|
||||||
|
msgstr "commandControl id is not specified"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "tobeDeleteObj is not a valid object"
|
||||||
|
msgstr "tobeDeleteObj is not a valid object"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Demo"
|
||||||
|
msgstr "Demo"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Demo Command"
|
||||||
|
msgstr "Demo Command"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Input: {} changed event triggered"
|
||||||
|
msgstr "Input: {} changed event triggered"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Input changed event failed: {}"
|
||||||
|
msgstr "Input changed event failed: {}"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "command: {} executed successfully"
|
||||||
|
msgstr "command: {} executed successfully"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "command executed failed: {}"
|
||||||
|
msgstr "command executed failed: {}"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Value"
|
||||||
|
msgstr "Value"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Checked"
|
||||||
|
msgstr "Checked"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Default value"
|
||||||
|
msgstr "Default value"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "String Value"
|
||||||
|
msgstr "String Value"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Select one"
|
||||||
|
msgstr "Select one"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Selection"
|
||||||
|
msgstr "Selection"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Drop Down"
|
||||||
|
msgstr "Drop Down"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Drop Down2"
|
||||||
|
msgstr "Drop Down2"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "ListItem 1"
|
||||||
|
msgstr "ListItem 1"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "ListItem 2"
|
||||||
|
msgstr "ListItem 2"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "ListItem 3"
|
||||||
|
msgstr "ListItem 3"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Slider"
|
||||||
|
msgstr "Slider"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Button Row"
|
||||||
|
msgstr "Button Row"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Panel command created successfully"
|
||||||
|
msgstr "Panel command created successfully"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Panel command created failed: {}"
|
||||||
|
msgstr "Panel command created failed: {}"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "QAT command created successfully"
|
||||||
|
msgstr "QAT command created successfully"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "QAT command created failed: {}"
|
||||||
|
msgstr "QAT command created failed: {}"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "A demo command button is successfully added to the Quick Access Toolbar"
|
||||||
|
msgstr "A demo command button is successfully added to the Quick Access Toolbar"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "A demo command is successfully added to the create panel in modeling workspace"
|
||||||
|
msgstr "A demo command is successfully added to the create panel in modeling workspace"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "AddIn Start Failed: {}"
|
||||||
|
msgstr "AddIn Start Failed: {}"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "AddIn Stop Failed: {}"
|
||||||
|
msgstr "AddIn Stop Failed: {}"
|
||||||
6
python/AddInSamples/AddInSample/help.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
This is help file of this addin.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
python/AddInSamples/AddInSample/resources/16x16-dark.png
Normal file
|
After Width: | Height: | Size: 726 B |
BIN
python/AddInSamples/AddInSample/resources/16x16-disabled.png
Normal file
|
After Width: | Height: | Size: 726 B |
BIN
python/AddInSamples/AddInSample/resources/16x16.png
Normal file
|
After Width: | Height: | Size: 708 B |
BIN
python/AddInSamples/AddInSample/resources/32x32-dark.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
python/AddInSamples/AddInSample/resources/32x32-disabled.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
python/AddInSamples/AddInSample/resources/32x32.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
python/AddInSamples/AddInSample/zh-CN/LC_MESSAGES/resource.mo
Normal file
136
python/AddInSamples/AddInSample/zh-CN/LC_MESSAGES/resource.po
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR ORGANIZATION
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"POT-Creation-Date: 2015-08-04 12:47+China Standard Time\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "commandDefinition id is not specified"
|
||||||
|
msgstr "没有指定commandDefinition id"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "commandControl id is not specified"
|
||||||
|
msgstr "没有指定commandControl id"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "tobeDeleteObj is not a valid object"
|
||||||
|
msgstr "tobeDeleteObj不是一个有效的对象"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Demo"
|
||||||
|
msgstr "演示"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Demo Command"
|
||||||
|
msgstr "演示命令"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Input: {} changed event triggered"
|
||||||
|
msgstr "触发事件Input: {} changed"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Input changed event failed: {}"
|
||||||
|
msgstr "输入变化事件失败: {}"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "command: {} executed successfully"
|
||||||
|
msgstr "命令: {} 执行成功"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "命令执行失败: {}"
|
||||||
|
msgstr "命令执行失败: {}"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Value"
|
||||||
|
msgstr "值"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Checked"
|
||||||
|
msgstr "选中"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Default value"
|
||||||
|
msgstr "默认值"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "String Value"
|
||||||
|
msgstr "字符串"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Select one"
|
||||||
|
msgstr "选一个"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Selection"
|
||||||
|
msgstr "选择"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Drop Down"
|
||||||
|
msgstr "下拉框"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Drop Down2"
|
||||||
|
msgstr "下拉框2"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "ListItem 1"
|
||||||
|
msgstr "列表项 1"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "ListItem 2"
|
||||||
|
msgstr "列表项 2"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "ListItem 3"
|
||||||
|
msgstr "列表项 3"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Slider"
|
||||||
|
msgstr "滚动条"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Button Row"
|
||||||
|
msgstr "按钮行"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Panel command created successfully"
|
||||||
|
msgstr "面板命令创建成功"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Panel command created failed: {}"
|
||||||
|
msgstr "面板命令创建失败: {}"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "QAT command created successfully"
|
||||||
|
msgstr "QAT命令创建成功"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "QAT command created failed: {}"
|
||||||
|
msgstr "QAT命令创建失败: {}"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "A demo command button is successfully added to the Quick Access Toolbar"
|
||||||
|
msgstr "一个演示命令被成功的添加到QAT"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "A demo command is successfully added to the create panel in modeling workspace"
|
||||||
|
msgstr "一个演示命令被成功的添加到创建面板"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "AddIn Start Failed: {}"
|
||||||
|
msgstr "附加模块启动失败: {}"
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "AddIn Stop Failed: {}"
|
||||||
|
msgstr "附加模块启动成功: {}"
|
||||||
17
python/AddInSamples/CustomWorkspaceSample/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python: Attach",
|
||||||
|
"type": "python",
|
||||||
|
"request": "attach",
|
||||||
|
"pathMappings": [ {
|
||||||
|
"localRoot": "${workspaceRoot}",
|
||||||
|
"remoteRoot": "${workspaceRoot}"}],
|
||||||
|
"osx": {"filePath":"${file}"},
|
||||||
|
"windows": {"filePath":"${file}"},
|
||||||
|
"port": 9000,
|
||||||
|
"host": "localhost"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
python/AddInSamples/SpurGear/Resources/GearEnglish.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
python/AddInSamples/SpurGear/Resources/GearMetric.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
python/AddInSamples/SpurGear/Resources/SpurGear/16x16.png
Normal file
|
After Width: | Height: | Size: 965 B |
BIN
python/AddInSamples/SpurGear/Resources/SpurGear/16x16@2x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
python/AddInSamples/SpurGear/Resources/SpurGear/32x32.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
python/AddInSamples/SpurGear/Resources/SpurGear/32x32@2x.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
13
python/AddInSamples/SpurGear/SpurGear.manifest
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"autodeskProduct": "Fusion360",
|
||||||
|
"type": "addin",
|
||||||
|
"id": "6b509eec-34ca-4044-875d-cfb30563b658",
|
||||||
|
"author": "",
|
||||||
|
"description": {
|
||||||
|
"": "Creates a spur gear component."
|
||||||
|
},
|
||||||
|
"version": "",
|
||||||
|
"runOnStartup": false,
|
||||||
|
"supportedOS": "windows|mac",
|
||||||
|
"editEnabled": true
|
||||||
|
}
|
||||||
772
python/AddInSamples/SpurGear/SpurGear.py
Normal file
@ -0,0 +1,772 @@
|
|||||||
|
#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
|
||||||
|
|
||||||
BIN
python/DLLs/_asyncio.pyd
Normal file
BIN
python/DLLs/_asyncio_d.pyd
Normal file
BIN
python/DLLs/_bz2.pyd
Normal file
BIN
python/DLLs/_bz2_d.pyd
Normal file
BIN
python/DLLs/_ctypes.pyd
Normal file
BIN
python/DLLs/_ctypes_d.pyd
Normal file
BIN
python/DLLs/_ctypes_test.pyd
Normal file
BIN
python/DLLs/_ctypes_test_d.pyd
Normal file
BIN
python/DLLs/_decimal.pyd
Normal file
BIN
python/DLLs/_decimal_d.pyd
Normal file
BIN
python/DLLs/_elementtree.pyd
Normal file
BIN
python/DLLs/_elementtree_d.pyd
Normal file
BIN
python/DLLs/_hashlib.pyd
Normal file
BIN
python/DLLs/_hashlib_d.pyd
Normal file
BIN
python/DLLs/_lzma.pyd
Normal file
BIN
python/DLLs/_lzma_d.pyd
Normal file
BIN
python/DLLs/_msi.pyd
Normal file
BIN
python/DLLs/_msi_d.pyd
Normal file
BIN
python/DLLs/_multiprocessing.pyd
Normal file
BIN
python/DLLs/_multiprocessing_d.pyd
Normal file
BIN
python/DLLs/_overlapped.pyd
Normal file
BIN
python/DLLs/_overlapped_d.pyd
Normal file
BIN
python/DLLs/_queue.pyd
Normal file
BIN
python/DLLs/_queue_d.pyd
Normal file
BIN
python/DLLs/_socket.pyd
Normal file
BIN
python/DLLs/_socket_d.pyd
Normal file
BIN
python/DLLs/_sqlite3.pyd
Normal file
BIN
python/DLLs/_sqlite3_d.pyd
Normal file
BIN
python/DLLs/_ssl.pyd
Normal file
BIN
python/DLLs/_ssl_d.pyd
Normal file
BIN
python/DLLs/_testbuffer.pyd
Normal file
BIN
python/DLLs/_testbuffer_d.pyd
Normal file
BIN
python/DLLs/_testcapi.pyd
Normal file
BIN
python/DLLs/_testcapi_d.pyd
Normal file
BIN
python/DLLs/_testconsole.pyd
Normal file
BIN
python/DLLs/_testconsole_d.pyd
Normal file
BIN
python/DLLs/_testimportmultiple.pyd
Normal file
BIN
python/DLLs/_testimportmultiple_d.pyd
Normal file
BIN
python/DLLs/_testmultiphase.pyd
Normal file
BIN
python/DLLs/_testmultiphase_d.pyd
Normal file
BIN
python/DLLs/_tkinter.pyd
Normal file
BIN
python/DLLs/_tkinter_d.pyd
Normal file
BIN
python/DLLs/libcrypto-1_1.dll
Normal file
BIN
python/DLLs/libssl-1_1.dll
Normal file
BIN
python/DLLs/py.ico
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
python/DLLs/pyc.ico
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
python/DLLs/pyd.ico
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
python/DLLs/pyexpat.pyd
Normal file
BIN
python/DLLs/pyexpat_d.pyd
Normal file
BIN
python/DLLs/python_lib.cat
Normal file
BIN
python/DLLs/python_tools.cat
Normal file
BIN
python/DLLs/select.pyd
Normal file
BIN
python/DLLs/select_d.pyd
Normal file
BIN
python/DLLs/sqlite3.dll
Normal file
BIN
python/DLLs/sqlite3_d.dll
Normal file
BIN
python/DLLs/tcl86t.dll
Normal file
BIN
python/DLLs/tk86t.dll
Normal file
BIN
python/DLLs/unicodedata.pyd
Normal file
BIN
python/DLLs/unicodedata_d.pyd
Normal file
BIN
python/DLLs/winsound.pyd
Normal file
BIN
python/DLLs/winsound_d.pyd
Normal file
10
python/Default/default.manifest
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"autodeskProduct":"Fusion360",
|
||||||
|
"type":"script",
|
||||||
|
"author":"Autodesk Inc.",
|
||||||
|
"description":{
|
||||||
|
"":"This is sample script."
|
||||||
|
},
|
||||||
|
"supportedOS":"windows|mac",
|
||||||
|
"editEnabled": true
|
||||||
|
}
|
||||||
15
python/Default/default.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#Author-
|
||||||
|
#Description-
|
||||||
|
|
||||||
|
import adsk.core, adsk.fusion, adsk.cam, traceback
|
||||||
|
|
||||||
|
def run(context):
|
||||||
|
ui = None
|
||||||
|
try:
|
||||||
|
app = adsk.core.Application.get()
|
||||||
|
ui = app.userInterface
|
||||||
|
ui.messageBox('Hello script')
|
||||||
|
|
||||||
|
except:
|
||||||
|
if ui:
|
||||||
|
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
|
||||||
17
python/Default/launch.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python: Attach",
|
||||||
|
"type": "python",
|
||||||
|
"request": "attach",
|
||||||
|
"pathMappings": [ {
|
||||||
|
"localRoot": "${workspaceRoot}",
|
||||||
|
"remoteRoot": "${workspaceRoot}"}],
|
||||||
|
"osx": {"filePath":"${file}"},
|
||||||
|
"windows": {"filePath":"${file}"},
|
||||||
|
"port": 9000,
|
||||||
|
"host": "localhost"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
python/DefaultAddIn/defaultaddin.manifest
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"autodeskProduct":"Fusion360",
|
||||||
|
"type":"addin",
|
||||||
|
"id":"319679C5-AF6D-4A46-862D-93414F1AE117",
|
||||||
|
"author":"Autodesk Inc.",
|
||||||
|
"description":{
|
||||||
|
"":"This is sample addin."
|
||||||
|
},
|
||||||
|
"version":"0.0.1",
|
||||||
|
"runOnStartup":false,
|
||||||
|
"supportedOS":"windows|mac",
|
||||||
|
"editEnabled": true
|
||||||
|
}
|
||||||
26
python/DefaultAddIn/defaultaddin.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#Author-
|
||||||
|
#Description-
|
||||||
|
|
||||||
|
import adsk.core, adsk.fusion, adsk.cam, traceback
|
||||||
|
|
||||||
|
def run(context):
|
||||||
|
ui = None
|
||||||
|
try:
|
||||||
|
app = adsk.core.Application.get()
|
||||||
|
ui = app.userInterface
|
||||||
|
ui.messageBox('Hello addin')
|
||||||
|
|
||||||
|
except:
|
||||||
|
if ui:
|
||||||
|
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
|
||||||
|
|
||||||
|
def stop(context):
|
||||||
|
ui = None
|
||||||
|
try:
|
||||||
|
app = adsk.core.Application.get()
|
||||||
|
ui = app.userInterface
|
||||||
|
ui.messageBox('Stop addin')
|
||||||
|
|
||||||
|
except:
|
||||||
|
if ui:
|
||||||
|
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
|
||||||
146
python/Lib/__future__.py
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
"""Record of phased-in incompatible language changes.
|
||||||
|
|
||||||
|
Each line is of the form:
|
||||||
|
|
||||||
|
FeatureName = "_Feature(" OptionalRelease "," MandatoryRelease ","
|
||||||
|
CompilerFlag ")"
|
||||||
|
|
||||||
|
where, normally, OptionalRelease < MandatoryRelease, and both are 5-tuples
|
||||||
|
of the same form as sys.version_info:
|
||||||
|
|
||||||
|
(PY_MAJOR_VERSION, # the 2 in 2.1.0a3; an int
|
||||||
|
PY_MINOR_VERSION, # the 1; an int
|
||||||
|
PY_MICRO_VERSION, # the 0; an int
|
||||||
|
PY_RELEASE_LEVEL, # "alpha", "beta", "candidate" or "final"; string
|
||||||
|
PY_RELEASE_SERIAL # the 3; an int
|
||||||
|
)
|
||||||
|
|
||||||
|
OptionalRelease records the first release in which
|
||||||
|
|
||||||
|
from __future__ import FeatureName
|
||||||
|
|
||||||
|
was accepted.
|
||||||
|
|
||||||
|
In the case of MandatoryReleases that have not yet occurred,
|
||||||
|
MandatoryRelease predicts the release in which the feature will become part
|
||||||
|
of the language.
|
||||||
|
|
||||||
|
Else MandatoryRelease records when the feature became part of the language;
|
||||||
|
in releases at or after that, modules no longer need
|
||||||
|
|
||||||
|
from __future__ import FeatureName
|
||||||
|
|
||||||
|
to use the feature in question, but may continue to use such imports.
|
||||||
|
|
||||||
|
MandatoryRelease may also be None, meaning that a planned feature got
|
||||||
|
dropped.
|
||||||
|
|
||||||
|
Instances of class _Feature have two corresponding methods,
|
||||||
|
.getOptionalRelease() and .getMandatoryRelease().
|
||||||
|
|
||||||
|
CompilerFlag is the (bitfield) flag that should be passed in the fourth
|
||||||
|
argument to the builtin function compile() to enable the feature in
|
||||||
|
dynamically compiled code. This flag is stored in the .compiler_flag
|
||||||
|
attribute on _Future instances. These values must match the appropriate
|
||||||
|
#defines of CO_xxx flags in Include/compile.h.
|
||||||
|
|
||||||
|
No feature line is ever to be deleted from this file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
all_feature_names = [
|
||||||
|
"nested_scopes",
|
||||||
|
"generators",
|
||||||
|
"division",
|
||||||
|
"absolute_import",
|
||||||
|
"with_statement",
|
||||||
|
"print_function",
|
||||||
|
"unicode_literals",
|
||||||
|
"barry_as_FLUFL",
|
||||||
|
"generator_stop",
|
||||||
|
"annotations",
|
||||||
|
]
|
||||||
|
|
||||||
|
__all__ = ["all_feature_names"] + all_feature_names
|
||||||
|
|
||||||
|
# The CO_xxx symbols are defined here under the same names defined in
|
||||||
|
# code.h and used by compile.h, so that an editor search will find them here.
|
||||||
|
# However, they're not exported in __all__, because they don't really belong to
|
||||||
|
# this module.
|
||||||
|
CO_NESTED = 0x0010 # nested_scopes
|
||||||
|
CO_GENERATOR_ALLOWED = 0 # generators (obsolete, was 0x1000)
|
||||||
|
CO_FUTURE_DIVISION = 0x2000 # division
|
||||||
|
CO_FUTURE_ABSOLUTE_IMPORT = 0x4000 # perform absolute imports by default
|
||||||
|
CO_FUTURE_WITH_STATEMENT = 0x8000 # with statement
|
||||||
|
CO_FUTURE_PRINT_FUNCTION = 0x10000 # print function
|
||||||
|
CO_FUTURE_UNICODE_LITERALS = 0x20000 # unicode string literals
|
||||||
|
CO_FUTURE_BARRY_AS_BDFL = 0x40000
|
||||||
|
CO_FUTURE_GENERATOR_STOP = 0x80000 # StopIteration becomes RuntimeError in generators
|
||||||
|
CO_FUTURE_ANNOTATIONS = 0x100000 # annotations become strings at runtime
|
||||||
|
|
||||||
|
class _Feature:
|
||||||
|
def __init__(self, optionalRelease, mandatoryRelease, compiler_flag):
|
||||||
|
self.optional = optionalRelease
|
||||||
|
self.mandatory = mandatoryRelease
|
||||||
|
self.compiler_flag = compiler_flag
|
||||||
|
|
||||||
|
def getOptionalRelease(self):
|
||||||
|
"""Return first release in which this feature was recognized.
|
||||||
|
|
||||||
|
This is a 5-tuple, of the same form as sys.version_info.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.optional
|
||||||
|
|
||||||
|
def getMandatoryRelease(self):
|
||||||
|
"""Return release in which this feature will become mandatory.
|
||||||
|
|
||||||
|
This is a 5-tuple, of the same form as sys.version_info, or, if
|
||||||
|
the feature was dropped, is None.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.mandatory
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "_Feature" + repr((self.optional,
|
||||||
|
self.mandatory,
|
||||||
|
self.compiler_flag))
|
||||||
|
|
||||||
|
nested_scopes = _Feature((2, 1, 0, "beta", 1),
|
||||||
|
(2, 2, 0, "alpha", 0),
|
||||||
|
CO_NESTED)
|
||||||
|
|
||||||
|
generators = _Feature((2, 2, 0, "alpha", 1),
|
||||||
|
(2, 3, 0, "final", 0),
|
||||||
|
CO_GENERATOR_ALLOWED)
|
||||||
|
|
||||||
|
division = _Feature((2, 2, 0, "alpha", 2),
|
||||||
|
(3, 0, 0, "alpha", 0),
|
||||||
|
CO_FUTURE_DIVISION)
|
||||||
|
|
||||||
|
absolute_import = _Feature((2, 5, 0, "alpha", 1),
|
||||||
|
(3, 0, 0, "alpha", 0),
|
||||||
|
CO_FUTURE_ABSOLUTE_IMPORT)
|
||||||
|
|
||||||
|
with_statement = _Feature((2, 5, 0, "alpha", 1),
|
||||||
|
(2, 6, 0, "alpha", 0),
|
||||||
|
CO_FUTURE_WITH_STATEMENT)
|
||||||
|
|
||||||
|
print_function = _Feature((2, 6, 0, "alpha", 2),
|
||||||
|
(3, 0, 0, "alpha", 0),
|
||||||
|
CO_FUTURE_PRINT_FUNCTION)
|
||||||
|
|
||||||
|
unicode_literals = _Feature((2, 6, 0, "alpha", 2),
|
||||||
|
(3, 0, 0, "alpha", 0),
|
||||||
|
CO_FUTURE_UNICODE_LITERALS)
|
||||||
|
|
||||||
|
barry_as_FLUFL = _Feature((3, 1, 0, "alpha", 2),
|
||||||
|
(3, 9, 0, "alpha", 0),
|
||||||
|
CO_FUTURE_BARRY_AS_BDFL)
|
||||||
|
|
||||||
|
generator_stop = _Feature((3, 5, 0, "beta", 1),
|
||||||
|
(3, 7, 0, "alpha", 0),
|
||||||
|
CO_FUTURE_GENERATOR_STOP)
|
||||||
|
|
||||||
|
annotations = _Feature((3, 7, 0, "beta", 1),
|
||||||
|
(4, 0, 0, "alpha", 0),
|
||||||
|
CO_FUTURE_ANNOTATIONS)
|
||||||
1
python/Lib/__phello__.foo.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# This file exists as a helper for the test.test_frozen module.
|
||||||