Saving multiple documents after updating user parameters

Saving multiple documents after updating user parameters

Anonymous
Not applicable
731 Views
2 Replies
Message 1 of 3

Saving multiple documents after updating user parameters

Anonymous
Not applicable

We are attempting to update and save Fusion 360 documents with user parameters via the Python API. Our current working design consists of a top-level assembly, with one child assembly (which itself has two child components) and one child component. We call our script from the top-level assembly and recursively update parameters based on an input YAML file. While we can update parameters for child components in the two assemblies, the changes do not persist in the child components. In addition, we want to be able to save these documents from the API if possible; we can only save the new changes after updating out-of-date components (which in theory there should be no out of date components if we make persistent updates).

 

Our script, depending on the value of a boolean variable, either takes in a YAML file to update the parameters of the assembly, or writes the assembly parameters to a YAML file. An example YAML file is shown below:

testAssem:
  dp:
    num_cylinders: '80'
  testCylinder:
    dp:
      circleDiam: 80 mm
      circleHeight: 100 mm
  testWheelAndBox:
    testBox:
      dp:
        boxHeight: 200 mm
        boxLength: 100 mm
        boxWidth: 150 mm
        circleDepth: 20 mm
        circleDiam: 80 mm
    testWheel:
      dp:
        circleDiam: 50 mm
        circleHeight: 20 mm

"dp" or "design parameters" is the key corresponding to user parameters that we want to change. Other key names like "testAssem" or "testWheel" refer to Fusion designs, which will have user parameters under "dp".

 

Our script is located below: 

def build_names_to_versions_helper(occurrences, input_dict):
    """
    Helper used to build dictionary mapping component names used in
    parameters file to component names used in Fusion 360, e.g.
    testAssem -> testAssem v14
    :param occurrences: Occurrences object used to access child occurrences
    :param input_dict: Python dict to update with new key-value pairs
    :return: The completed mapping of param component names to Fusion component names
    """
    for i in range(0, occurrences.count):
        occ = occurrences.item(i)
        full_name = occ.name
        name_parts = full_name.split(" ")
        name_only = name_parts[0]
        input_dict[name_only] = full_name

        if occ.childOccurrences:
            child_dict = build_names_to_versions_helper(occ.childOccurrences, input_dict)
            child_copy = child_dict.copy()
            input_dict.update(child_copy)

    return input_dict


def build_names_to_versions(root_component):
    """
    Builds a mapping from parameter file component names to versioned Fusion 360
    component names, e.g. testAssem -> testAssem v14
    :param root_component: Root component of Fusion 360 assembly
    :return: Dictionary mapping parameter component names to Fusion 360 component names
    """
    input_dict = {}
    full_name = root_component.name
    name_parts = full_name.split(" ")
    name_only = name_parts[0]
    input_dict[name_only] = full_name

    occurrences = root_component.occurrences.asList
    return build_names_to_versions_helper(occurrences, input_dict)


def build_orig_params_helper(root_component):
    ret = {}
    if hasattr(root_component, "occurrences"):
        for i in range(root_component.occurrences.count):
            occurrence = root_component.occurrences.item(i)
            full_name = occurrence.name
            name_parts = full_name.split(" ")
            name_only = name_parts[0]
            ret[name_only] = build_orig_params_helper(occurrence.component)

    params = {}
    for i in range(root_component.parentDesign.userParameters.count):
        param = root_component.parentDesign.userParameters.item(i)
        params[param.name] = param.expression

    if root_component.parentDesign.userParameters.count > 0:
        ret["dp"] = params

    return ret

def build_orig_params(root_component):
    result = build_orig_params_helper(root_component)
    name = root_component.name.split(" ")[0]
    return { name : result }

def change_param_value(root_component, param_name, param_value):
    """
    Update a Fusion 360 component's user parameter corresponding to
    param_name with expression param_value
    :param root_component: Component whose parameter is to be updated
    :param param_name: Name of user parameter to update
    :param param_value: New expression for user parameter
    :return: None
    """

    #root_component.parentDocument.activate
    root_component.parentDesign.userParameters.itemByName(param_name).expression = param_value
    #root_component.parentDocument.save
    print("Updated component: {} {}={}".format(root_component.name, param_name, param_value))


def update_user_params(root_component, yaml_dict, component_names_to_versions):
    """
    Updates a Fusion 360 assembly, with root_component as the root
    component, by setting the user parameters using the structure in yaml_dict,
    with name mappings in component_names_to_versions
    :param root_component: Root component of Fusion 360 assembly
    :param yaml_dict: Python dict built from yaml parameter file specifying which parameters to change
    :param component_names_to_versions: Python dict mapping parameter file component names to Fusion 360 component names
    :return: None
    """
    for prop in list(yaml_dict.keys()): #testAssem
        if prop == "dp":
            # modify this component's params
            user_params = yaml_dict[prop]
            for param in list(user_params.keys()):
                expression = user_params[param]
                change_param_value(root_component, param, expression)

        elif prop == "hp":
            break
        else:
            # it's a component, not user params
            component_name = prop
            true_name = component_names_to_versions[component_name]
            working_occurrence = root_component.occurrences.asList.itemByName(true_name)

            if working_occurrence:
                # that means we are not operating at the highest level, i.e. it's a child occurrence
                update_user_params(working_occurrence.component,
                              yaml_dict[prop], component_names_to_versions)
            else:
                # it's the root component
                update_user_params(root_component, yaml_dict[prop], component_names_to_versions)


def run(context):
    """
    Fusion 360 entry point. This script updates the user parameters
    of a Fusion 360 assembly using the structure specified in the yaml file
    located at the hard-coded file path.
    :param context: Fusion 360 context
    :return: None
    """
    save_or_open = 1 #1 for save yaml, 0 for open yaml and change params    
    
    ui = None
    print("IS THIS WORKING")
    try:
        app = adsk.core.Application.get()
        ui = app.userInterface
        product = app.activeProduct
        design = adsk.fusion.Design.cast(product)
        if not design:
            ui.messageBox('No active Fusion design', 'No Design')
            return

        # Get the root component of the active design.
        root_component = design.rootComponent

        #extract parameters from Fusion into a dictionary
        assembly_dict = build_orig_params(root_component)

        print("--------------------------")
        print(json.dumps(assembly_dict, indent=4))

        if(save_or_open == 1):
            #Saves all the parameters in the assembly into a yaml file
            yamlSaveDialog = ui.createFileDialog()
            yamlSaveDialog.isMultiSelectEnabled = False
            yamlSaveDialog.title = "Specify yaml save file"
            yamlSaveDialog.filter = 'yaml files (*.yaml)'
            yamlSaveDialog.filterIndex = 0
            saveDialogResult = yamlSaveDialog.showSave()
            if saveDialogResult == adsk.core.DialogResults.DialogOK:
                yaml_out_file = yamlSaveDialog.filename
            else:
                return
            
            with open(yaml_out_file, 'w+') as yamlOut:
                yaml.dump(assembly_dict, yamlOut, default_flow_style=False)
 
        else:
            component_names_to_versions = build_names_to_versions(root_component)
            print(json.dumps(component_names_to_versions))
            
    #       Takes in a yaml parameter file to change the parameters in assembly file into
            yamlFileDialog = ui.createFileDialog()
            yamlFileDialog.isMultiSelectEnabled = False
            yamlFileDialog.title = "Specify yaml parameter file"
            yamlFileDialog.filter = 'yaml files (*.yaml)'
            yamlFileDialog.filterIndex = 0
            takeDialogResult = yamlFileDialog.showOpen()
            if takeDialogResult == adsk.core.DialogResults.DialogOK:
                yaml_file_path = yamlFileDialog.filename
            else:
                return
            
            with open(yaml_file_path, "r") as f:
                doc = yaml.load(f)
                update_user_params(root_component, doc, component_names_to_versions)

    except:
        print(traceback.format_exc())

TL;DR: We want to be able to update Fusion parameters via the Python API and SAVE the changes, and make sure the parameter updates are persistent through child components. 

0 Likes
732 Views
2 Replies
Replies (2)
Message 2 of 3

lichtzeichenanlage
Advisor
Advisor

What your are planing to do sounds like Fusion Sheeter. The difference is, that Sheeter uses Google Docs to maintain different sizes and not YAML

What do you mean with child components? (Copied) components within a design or linked components?

0 Likes
Message 3 of 3

Anonymous
Not applicable

Hi, thank you for your reply. I will check out Sheeter in the meantime. 

By child components, I am referring to linked components.

0 Likes