Manipulating user parameters and preserving dependency coherence regardless of timeline position

Manipulating user parameters and preserving dependency coherence regardless of timeline position

bradandersonjr
Enthusiast Enthusiast
361 Views
1 Reply
Message 1 of 2

Manipulating user parameters and preserving dependency coherence regardless of timeline position

bradandersonjr
Enthusiast
Enthusiast

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!

Brad Anderson Jr
Fusion 360 Hobbyist
Fusion 360 Luthiers Facebook Group
0 Likes
362 Views
1 Reply
Reply (1)
Message 2 of 2

kandennti
Mentor
Mentor

Hi @bradandersonjr -San.

 

That is an interesting script.

 

I don't have data with many user parameters at hand, so I can't try, but why not move the timeline marker to the beginning and run the Design.computeAll method?

https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-09EC1F91-0E82-4B05-A7E8-AC1730ECED39 

0 Likes