[SHARING] Remembering data shapes input for next run

Hey all,

I wanted to share something i’ve built i assume others might find useful.

When using datashapes, I’ve always felt like persistent inputs were missing. So if a user runs the graph with dynamo player twice, you’d need to click all the values each time, while some inputs almost never change. See below as example:

When running the graph for the first time, default values are applied:
image
You can then set the values however you want and continue.
The next time you run the graph, the default values are set to whatever you’ve set the last time:
image

This works by using two python scripts, one to save/load a .json file in the user’s appdata folder. The filename is the same as the graph you’re currently using. This python node can be toggled to either save or load a config (you load the config at the start of the run, and overwrite the config at the end with the new user input)

A second python node is used to get the default values and prevent a bunch of errors if you were to do this with OOTB nodes:

The node returns either a value from the loaded config (if the config exists and the index exists) or it supplies the default value like you’d normally would on the datashapes input node. You can also supply the python node with the array of values going into the input node so it returns a default index instead of default value.

The python script for loading/saving config:

# Load the Python Standard and DesignScript Libraries
import sys
import clr
import os
import json
clr.AddReference('DynamoRevitDS')
clr.AddReference('ProtoGeometry')

import Dynamo 
from Autodesk.DesignScript.Geometry import *
dynamoRevit = Dynamo.Applications.DynamoRevit()

operation = IN[0]
data = IN[1]
runIt = IN[2]

def get_graph_name():
    # Copied this function from John Pierson https://github.com/Amoursol/dynamoPython/blob/master/dynamoAPI/dynamoAPICurrentGraphName.py
    dynamoRevit = Dynamo.Applications.DynamoRevit()
    currentWorkspace = dynamoRevit.RevitDynamoModel.CurrentWorkspace
    
    # Access current version of Dynamo
    version = dynamoRevit.RevitDynamoModel.Version
    
    # Checks version of Dynamo and adjusts output according to version
    if version.startswith("1."):
        # Gets file name which includes full path
        filename = currentWorkspace.FileName
        
        # Splits out file path to just file name
        graphName = filename.Split("\\")[-1].Replace(".dyn", "")
    
    elif version.startswith("2."):
        graphName = currentWorkspace.Name
    
    else:
        # Just returning a string here so this name will be used instead of breaking the rest of the script
        graphName = "unsupported version"
    
    return graphName

def get_filepath():
    # Generating filepath for the config.json file.
    return os.getenv('APPDATA') + "\\Dynamo\\Default Configs\\" + get_graph_name() + ".json"

def get_operation(operation):
    if str(operation).lower() == 'save':
        return operation
    elif str(operation).lower() == 'load':
        return operation
    else:
        return None

def save_config(data):
    
    file_path = get_filepath()
    directory = os.path.dirname(file_path)
    
    # Check if the directory exists; if not, create it
    if not os.path.exists(directory):
        os.makedirs(directory)
    
    # Open the file and write the JSON data
    with open(file_path, 'w') as f:
        f.write(json.dumps(data))

def load_config():
    file_path = get_filepath()
    # Check if the file exists
    if not os.path.isfile(file_path):
        return []
    
    with open(file_path, 'r') as f:
        data = json.load(f)
    
    # Ensure the data is a list
    if isinstance(data, list):
        return data
    else:
        # Handle unexpected formats
        return []

if runIt == True:
    if get_operation(operation) == 'save':
        if isinstance(data, list) and data:
            save_config(data)
            OUT = data
        else:
            OUT = "Data was empty or not valid list."
    
    if get_operation(operation) == 'load':
        OUT = load_config()
else:
    OUT = "Set runIt to True!"

The python script to return the correct default value:

# Load the Python Standard and DesignScript Libraries
import sys
import clr
import os

data = IN[0]
inputIndex = IN[1]
defaultValue = IN[2]
listOfValues = IN[3]

def get_value_index(value):
    if isinstance(listOfValues, list) and value in listOfValues:
        return listOfValues.index(value)
    else:
        return 0

if isinstance(data, list) and data:
    if not isinstance(listOfValues, list):
        try:
            OUT = data[inputIndex]
        except IndexError:
            OUT = defaultValue
    elif isinstance(listOfValues, list):
        value = data[inputIndex] if inputIndex < len(data) else defaultValue
        OUT = get_value_index(value)
    else:
        OUT = defaultValue
else:
    OUT = defaultValue

I hope it’s helpful for anyone.
Criticism is also welcome :slight_smile:
default input saving5.dyn (74.3 KB)

5 Likes

Great work!

@mostafa.elayoubi.ds this might make for a good update or additional feature!

2 Likes

Thank you! Your input was very helpful while I was building this!

1 Like

Just to add,

There are still some things i need to solve. Since elements aren’t JSON serializable, the script will be unable to save the config file if you feed it with views/export settings etc.

Would love some suggestions on how to deal with this.
I assume i should just use the corresponding element ID and just feed that into the script, and select element by ID when loading the presets.
But since i’m storing json arrays instead of objects i don’t have a good way of telling what’s an int and what’s an ID, unless i were to use keys {value: 2021921} and {id: 19118992}.

thing.__class__ == Element should get you started on that. However I would recommend checking to see if thing is a class you can serialize instead of something else first.

I believe that Datashapes works in Dynamo for Civil 3D so you’d want to address AutoCAD objects as well.

Also you’ll likely want to store the object type in the JSON to simplify conversion back; could be by storing a list of a dictionary for each item.

(Yes, all of this expands your work a good bit).

2 Likes

2 Likes