Manipulating user parameters and preserving dependency coherence regardless of timeline position
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
Hey! Long time, no post...
I'm currently working on a script that performs a basic conversion of units for user parameters.
Currently, the logic is:
- Iterate through all the user parameters and store them in a dictionary-- Name / Unit / Expression / Comment
- Append a suffix to the name-- So Length becomes Temp_Length [ this also changes the names of all references in expressions ]
- Create copies of all the original user parameters with their unit converted-- if Temp_Length is 'in', new Length is 'mm'
- Iterate through all the expressions removing Temp_ from all parameters-- this effectively changes the dependency to the new/copied user parameters.
- Delete all of the original user parameters.
Currently, it works well enough, but there are obvious issues. Any fractions are evaluated into floats. Not all expressions are properly converted either. The values are proper, just the expressions might still be in the old unit. The primary focus is the conversion of the unit and the values maintaining the correct distance.
My challenge at hand is how to deal with this with larger documents with many parameters. I'm testing this on a fretboard file of mine with 135 user parameters and it takes a while to chug through them all depending on the timeline marker.
I've noticed this breaks depending on where the timeline marker is. Obviously, if I move to the very end of the timeline everything updates properly, it just takes an incredible amount of time because everything has to be iterated through. If I move the marker to the beginning of the timeline, certain user parameters still are referenced from "future" parameters and can't be deleted because they're not properly being 'relinked' and still have a dependency.
I'm looking for suggestions on how to possibly navigate this. Ideally, if I could move to the beginning of the timeline, and somehow "freeze" any dependency that appears later on that'd be great!
import adsk.core, adsk.fusion, adsk.cam, traceback
# Global list to keep all event handlers in scope.
# This is only needed with Python.
handlers = []
def run(context):
ui = None
try:
app = adsk.core.Application.get()
ui = app.userInterface
# Get the CommandDefinitions collection.
cmdDefs = ui.commandDefinitions
# Create a button command definition.
button = cmdDefs.addButtonDefinition('UnitConversionButtonId', 'Unit Conversion', 'Convert units')
# Connect to the command created event.
CommandCreated = CommandCreatedEventHandler()
button.commandCreated.add(CommandCreated)
handlers.append(CommandCreated)
# Execute the command.
button.execute()
# Keep the script running.
adsk.autoTerminate(False)
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
# Event handler for the commandCreated event.
class CommandCreatedEventHandler(adsk.core.CommandCreatedEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
eventArgs = adsk.core.CommandCreatedEventArgs.cast(args)
cmd = eventArgs.command
# Connect to the execute event.
onExecute = CommandExecuteHandler()
cmd.execute.add(onExecute)
handlers.append(onExecute)
# Event handler for the execute event.
class CommandExecuteHandler(adsk.core.CommandEventHandler):
def __init__(self):
super().__init__()
def notify(self, args):
try:
# Get the Fusion 360 active app
app = adsk.core.Application.get()
design = app.activeProduct
# Check if the active product is a Fusion 360 design
design_type = design.objectType
if design_type != 'adsk::fusion::Design':
ui.messageBox('This command is applicable to Fusion 360 designs only.')
return
# Change the document's units
units_manager = design.fusionUnitsManager
current_units = units_manager.distanceDisplayUnits
# Initialize the user interface
ui = app.userInterface
if current_units == adsk.fusion.DistanceUnits.InchDistanceUnits:
# If the current units are inches, prompt to switch to millimeters
msg_result = ui.messageBox(
'Current units: Inches\n\nClick OK to switch to millimeters.',
'Unit Conversion',
adsk.core.MessageBoxButtonTypes.OKCancelButtonType
)
if msg_result == adsk.core.DialogResults.DialogOK:
units_manager.distanceDisplayUnits = adsk.fusion.DistanceUnits.MillimeterDistanceUnits
else:
# Cancel button or 'Esc' keypress was pressed
return
elif current_units == adsk.fusion.DistanceUnits.MillimeterDistanceUnits:
# If the current units are millimeters, prompt to switch to inches
msg_result = ui.messageBox(
'Current units: Millimeters\n\nClick OK to switch to inches.',
'Unit Conversion',
adsk.core.MessageBoxButtonTypes.OKCancelButtonType
)
if msg_result == adsk.core.DialogResults.DialogOK:
units_manager.distanceDisplayUnits = adsk.fusion.DistanceUnits.InchDistanceUnits
else:
# Cancel button or 'Esc' keypress was pressed
return
# Get the user parameters
user_parameters = design.userParameters
# Store the original user parameters in a dictionary
original_parameters = {
param.name: {
'Name': param.name,
'Unit': param.unit,
'Value': param.value,
'Expression': param.expression,
'Comment': param.comment
} for param in user_parameters
}
# Create a progress dialog for renaming parameters if there are parameters to rename
if len(original_parameters) > 0:
rename_dialog = ui.createProgressDialog()
rename_dialog.isBackgroundTranslucent = False
rename_dialog.show('Unit Conversion', 'Renaming parameters...', 0, len(original_parameters), 1)
# Rename the original user parameters with the "Temp_" prefix
for param in user_parameters:
param.name = f"Temp_{param.name}"
rename_dialog.progressValue += 1
# Hide the rename dialog
rename_dialog.hide()
# Function to check if the expression contains any parameter names
def contains_parameter_name(expression, param_names):
return any(name in expression for name in param_names)
# Create a progress dialog for creating new parameters
create_dialog = ui.createProgressDialog()
create_dialog.isBackgroundTranslucent = False
create_dialog.show('Unit Conversion', 'Creating new parameters...', 0, len(original_parameters), 1)
# Create new parameters based on the existing user parameters using original names
for index, (key, value) in enumerate(original_parameters.items()):
new_name = value['Name']
new_expression = value['Expression']
new_comment = value['Comment']
new_unit = value['Unit']
new_value = value['Value']
# Change the unit if it is 'in' or 'mm'
if new_unit == 'in':
new_unit = 'mm'
elif new_unit == 'mm':
new_unit = 'in'
# Check if the expression contains any parameter names
if contains_parameter_name(new_expression, original_parameters.keys()):
# Copy the expression as is and change the unit
user_parameters.add(
new_name,
adsk.core.ValueInput.createByString(new_expression),
new_unit,
new_comment
)
else:
# Convert the value
if new_unit == 'mm':
new_value *= 1 # Convert inches to mm
else:
new_value /= 1 # Convert mm to inches
# Add the new parameter with the converted value
user_parameters.add(
new_name,
adsk.core.ValueInput.createByReal(new_value),
new_unit,
new_comment
)
create_dialog.progressValue += 1
# Hide the create dialog
create_dialog.hide()
# Create a progress dialog for modifying parameter expressions
modify_dialog = ui.createProgressDialog()
modify_dialog.isBackgroundTranslucent = False
modify_dialog.show('Unit Conversion', 'Modifying parameter expressions...', 0, len(design.allParameters), 1)
# Iterate through allParameters and modify the expressions that reference user parameters
for parameter in design.allParameters:
referenced_user_parameters = [user_param.name for user_param in user_parameters if
user_param.name in parameter.expression]
if referenced_user_parameters:
modified_expression = parameter.expression
for param_name in referenced_user_parameters:
modified_expression = modified_expression.replace(f"Temp_{param_name}", param_name)
try:
parameter.expression = modified_expression
except RuntimeError as e:
ui.messageBox('Failed to set expression for parameter {}\nExpression: {}\nError: {}'.format(
parameter.name, modified_expression, str(e))
)
modify_dialog.progressValue += 1
# Hide the modify dialog
modify_dialog.hide()
# Create a progress dialog for deleting original parameters if there are parameters to delete
if len(original_parameters) > 0:
delete_dialog = ui.createProgressDialog()
delete_dialog.isBackgroundTranslucent = False
delete_dialog.show('Unit Conversion', 'Deleting original parameters...', 0, len(original_parameters), 1)
# Delete all original user parameters
for param_name in original_parameters.keys():
design.allParameters.itemByName(f"Temp_{param_name}").deleteMe()
delete_dialog.progressValue += 1
# Hide the delete dialog
delete_dialog.hide()
except:
if adsk.core.Application.get().userInterface:
adsk.core.Application.get().userInterface.messageBox('Failed:\n{}'.format(traceback.format_exc()))
# Force the termination of the command.
adsk.terminate()
def stop(context):
try:
app = adsk.core.Application.get()
ui = app.userInterface
# Delete the command definition.
cmdDef = ui.commandDefinitions.itemById('UnitConversionButtonId')
if cmdDef:
cmdDef.deleteMe()
except:
if ui:
ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
I'm also open to any suggestions on how to improve this-- thanks!