Batch update Dynamo graphs' code blocks

Hey everyone, for my company’s pyRevit hosted Toolbar I added some code block and a string so I can track when the scripts are ran.

However I added the code block and string to every script. I know there is a way to use a Custom Node, but I don’t want to have to publish an entire package and have every user install it.

So right now I am building a script to correct the graphs codeblocks but I’m not sure what to write or how to replace the right text; as I do not know how to interpreted Dynamo graphs turned into a string :smiling_face_with_tear:

I mean, if you move the package to user’s local disc directly this isn’t an issue. And someone is managing your network and the task of setting up something to ensure a file gets copied to the local disc is a less than five minute task for them - it will likely take you longer to explain what you want copied. And as you’re using design script directly I think you could publish the package once and never again. This is because design script is probably the most stable of the scripting tools out there as it has not changed since Dynamo 0.4 - my dyf nodes from 2018 still run in 3.2, but I might have jinxed that by saying this…

But for bulk copying a node into check out the below post which has a tool to insert a tracker into every graph in a directory. If you want to make it a code block you’ll likely need to do a bit more work. Python is there for you to review/work with so likely you can figure the missing bit(s) out.

I think I will stick with the classic: string.replace
to replace the Code Block contents. Now I just need to get all that I need into 1 Code Block.

Is there a Code Block language way to get the current Dynamo Script name?

Nope. Search the forum there are several examples of this - you’ll need to move to C# or Python. I think the tracker I posted might help too. Keeping separate CSV files for each might DYN can also be advisable.

1 Like

Ah oke, then I will just convert the Code Block contents into a python Node and replace the whole thing by that through String.Replace

# Add references to Revit and Dynamo libraries
clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *

clr.AddReference('DynamoRevitDS')
import Dynamo

clr.AddReference('DSCoreNodes')
import DSCore

from System.IO import Path

# Inputs
# tekstbestandpath: Input file path where the text will be appended
# This should be provided as an input in Dynamo.

# Get the Dynamo workspace name
dynamoRevit = Dynamo.Applications.DynamoRevit()
currentWorkspace = dynamoRevit.RevitDynamoModel.CurrentWorkspace
dynamoname = currentWorkspace.Name

# Get the current Revit document
doc = DocumentManager.Instance.CurrentDBDocument

# Get the Revit user's name
username = doc.Application.Username

# Get the Revit document's file path and name
filepath = doc.PathName
filename = Path.GetFileName(filepath) if filepath else "Untitled"

# Get the current date and time
current_datetime = DSCore.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")

# Create the log entry if the username is not "Thijs.vE"
if username != "Thijs.vE":
    log_entry = f"\n{current_datetime} - {filename} - {dynamoname}"
    # Append the log entry to the text file
    with open(tekstbestandpath, 'a') as file:
        file.write(log_entry)

Chatgpt gave me this. Is there a way to ‘spellcheck’ ? :sweat_smile:

EDIT:

Alright the script actually works. Not this code, as I needed an input. Never had a chatgpt provide an accurate Python node script so that’s a first.

Now onto implementing it by replacing old code blocks with this Python Node.

This is going to be a limitation. Look for the other method around the Dynamo workspace. GPT won’t help here so try importing Dynamo and using OUT = dir(Dynamo) until you find ExecutionEvents, Engine, and the like. One of those has a property which returns the workspace name.

Also I wouldn’t exclude yourself from the logger; the goal of these is typically to justify the automation by comparing the number of uses multiplied by the time savings per use to the time you take to build it. You’re more apt to use your graphs than another user so by excluding yourself you hamper the ability to show ROI. I have seen many instances where people were able to show ‘even if we only used our own graphs we are more profitable than working via the UI’ with this in place.

I am excluding myself because I want to log whether the scripts are used by anyone else at all. Otherwise I am wasting time to maintain the toolbar :wink:

It’s for me to know.

I am using this tracker to learn more about python and writing to external textfiles. Now I soon can add batch changing Dynamo scripts to the mix :smiley:

As for

I borrowed that from @GavinCrump’s python node behind: Current.Script :sweat_smile:

1 Like

Try this, which I believe should work for all supported build (2022 on):

clr.AddReference('DynamoServices') #add the dynamo services library to the CLR
from Dynamo.Events import * #import the events class to the Python environment

currentDynFilePath = ExecutionEvents.ActiveSession.CurrentWorkspacePath #get the path of the active Dynamo file

image

I am more stuck on editing the Dynamo script codes. As they contain unique Id’s :frowning:

Not sure how to circumvent that and delete that.

Is there a way to grab and entire string starting with X and ending with Y?

To more efficiently manage your tracker I recommend keeping them outside the main loop of the code. I also recommend using the same ‘blocks’ for your tracker in all graphs. This means you cannot populate your tracker manually, but must use the method in the thread I posted above. This will make the nodes you’re editing consistent in ALL graphs except for their location, which is in an altogether different section of the JSON.

That said, the answer is to parse the JSON object, find the object in question, and edit the desired properties accordingly. Not an easy task and it is very easy to put your library into a state of no-return, so be sure to keep a read only backup somewhere.

Alright so to summarize:

I tried to overwrite all my dynamo files that have a certain code block → no success as I still needed a string to feed the tracker-textfile path. This would get disconnected when I replaced the code block.

Then I asked ChatGPT to write me some python code, this was succesful.
I tried changing the code blocks into the python blocks. This I could not get done without breaking the scripts (I tested it in safe environment first)

So now I manually deleted the old tracker and pasted the python node in all the scripts. To test I altered my Revit username in the python code so the tracker would activate. After testing I then went back to my Overwrite all my dynamo scripts script to alter all python code content. This was succesful too :smiley:

So it was tedious especially because I had to open and alter 31 scripts manually. But now it is overwrite proof as all I need to alter is some python text.

So no JSON or parsing which I don’t understand anyway. I might still have to replace the text of what you wrote above about

> clr.AddReference('DynamoServices') #add the dynamo services library to the CLR
> from Dynamo.Events import * #import the events class to the Python environment
> 
> currentDynFilePath = ExecutionEvents.ActiveSession.CurrentWorkspacePath #get the path of the active Dynamo file

But to be honest it works and I can’t be bothered :stuck_out_tongue:

This is what the code in the zip files I posted does… in a single click. :melting_face:

Actually that didn’t come with the Custom Node file so that didn’t work :wink:

Not to mention I also had to purge the previously placed tracker

Okay so I got curious and consulted chatgpt. It explained JSON and it also explained your add tracker script. It looks pretty good for adding a template script to all other scripts.

But I think it is only adding.

Removing would be trickier because of the unique Id’s.

So if I add template style things, the Adding tracker script seems quite useful! Thank you!

If you know the code you’re looking for, the the property which stores said code, identifying the node in question is pretty easy. Once identified you can remove it entirely and replace with your own new one from your tracker file, or edit it to contain the code you want.

Not super easy, but not over the top either. The comments in there should make adding such pretty easily doable, though you may have to work through some parts in a bit of detail.

To add to this, a few options to explore include:

  • Custom package approach where the custom node contains your tracker code and runs on passthrough of data, chuck it at the end of script.
  • Write your code in a python node that appends in py code from a server or commonly accessible location, then it runs a function in the py file that you can update in future if needed without having to modify the scripts themselves.

Yeah a custom node for this instance is not the right route. It would be if I had authority etc to install packages in the background but I don’t have the means.

I think the 2nd option could be a good one. How would I go about doing that?

2 Likes

Side note:
image
The double \ to indicate a single . I saw it appear yesterday when I was editing the Dynamo language and saw the string of the filepath.

Does this work in codeblocks as well? I will have to test. As "" cannot be used to replace things haha. It is strange how the Dynamo file as text has this, but when I open the python nodes they don’t need the double \

Main topic:
I got it to work. I used my previous script to replace the old python nodes to the new: “call external python file”
Now I can freely adjust my external python file.

But I encountered a new issue. The project file from which the tracker runs still has the user name attached to it (because of the local file). How do I remove it in favor of anonimity? Can I also find the Central file instead?

The external python file that ChatGPT came up with and doesn’t really work…:

import clr
import os

# Add references to Revit and Dynamo libraries
clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *

clr.AddReference('DynamoRevitDS')
import Dynamo

clr.AddReference('DSCoreNodes')
import DSCore

from System.IO import Path

# Define the path to the text file within the script
tekstbestandpath = REDACTED

# Ensure the directory exists
os.makedirs(os.path.dirname(tekstbestandpath), exist_ok=True)

def remove_username_suffix(filename):
    # Separate the base filename and the extension
    base, ext = os.path.splitext(filename)
    
    # Split the base filename by underscores
    parts = base.split('_')
    
    # Check if the last part is a valid username (assuming usernames are alphabetical)
    if parts and parts[-1].isalpha():
        # Remove the last part and join the remaining parts
        base = '_'.join(parts[:-1])
    
    # Recombine the cleaned base filename with the extension
    return base + ext

try:
    # Get the Dynamo workspace name
    dynamoRevit = Dynamo.Applications.DynamoRevit()
    currentWorkspace = dynamoRevit.RevitDynamoModel.CurrentWorkspace
    dynamoname = currentWorkspace.Name

    # Get the current Revit document
    doc = DocumentManager.Instance.CurrentDBDocument

    # Get the Revit user's name
    username = doc.Application.Username

    # Get the central model file path
    if doc.IsWorkshared:
        central_model_path = doc.GetWorksharingCentralModelPath()
        filename = Path.GetFileName(central_model_path) if central_model_path else "Untitled"
    else:
        filename = Path.GetFileName(doc.PathName) if doc.PathName else "Untitled"

    # Remove the username suffix from the filename
    filename = remove_username_suffix(filename)

    # Get the current date and time
    current_datetime = DSCore.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")

    # Create the log entry if the username is not "REDACTED"
    if username != "REDACTED":
        log_entry = f"{current_datetime} - {filename} - {dynamoname}\n"
        print(f"Log Entry: {log_entry}")  # Print log entry for debugging

        # Append the log entry to the text file
        with open(tekstbestandpath, 'a') as file:
            file.write(log_entry)

except Exception as e:
    print(f"An error occurred: {e}")

Honestly, if I were you, I’d try to trim down ChatGPT usage to an absolute minimum… Your learning process is extremely impaired if you let Chatgippity write code for you that doesnt work and then ask users here why the code made by an autofill machine doesn’t work. I did the same when i started learning Python/Javascript. And believe me its so much easier once you start understanding the language compared to endless prompting when chatgpt doesnt do exactly what you asked.

You can use filename.replace(username, “”) for that.

Also you could get the workshared central model path like so:

# Import RevitAPI
import clr
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import ModelPathUtils

# Import DocumentManager
Test = IN[0]
Bestandslocatie = IN[1]

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager





if Test == True:
        OUT = ModelPathUtils.ConvertModelPathToUserVisiblePath(DocumentManager.Instance.CurrentDBDocument.GetWorksharingCentralModelPath())

else:
        OUT =Bestandslocatie

image

1 Like