Automating Project Setup with Python

I recently jumped back in to using Dynamo and started working on creating a graph to automate my firm’s project setup. We’re multi-disciplinary and setup 4 revit files at once. Instead of starting each new project from a template, we use the template to create a workshared central model for each discipline. Each new project is then started from a copy of those workshared models.

We the graph to: open, audit, detach, and save each new central model while updating some project information parameters, updating the revit links to the other disciplines, and change the user worksets to not editable.

I had some issues trying to get the graph to update the project information parameters and change the worksets to not editable so I saw this as an opportunity to get better at Python.

About a hundred iterations later. I’ve had varying success with opening, saving, and changing parameter names but cannot seem to get them all working in on script. Below is my best effort to date. I’m having trouble:

  1. Getting the file to save.
  2. Getting any other Project Information parameter other than PROJECT NAME.

Any direction anyone can provide would be great. I’ve looked through a ton of forum posts, blogs, and tutorials to get this far.

#List of Node Inputs
template_docs = IN[0]
project_docs = IN[1]
params_to_modify = IN[2]
new_param_values = IN[3]
file_audit_bool = IN[4]
overwrite_exist_file_bool = IN[5]
saveas_newcentral_bool = IN[6]

#List of Variables
open_options = OpenOptions()
open_options.Audit = file_audit_bool
open_options.DetachFromCentralOption = DetachFromCentralOption.DetachAndPreserveWorksets

saveas_options = SaveAsOptions()
saveas_options.OverwriteExistingFile = overwrite_exist_file_bool

workshare_saveas_options = WorksharingSaveAsOptions()
workshare_saveas_options.SaveAsCentral = saveas_newcentral_bool
saveas_options.SetWorksharingOptions(workshare_saveas_options)

report = []

#Actual Work 
if template_docs == 'Current.Document':
    template_docs = DocumentManager.Instance.CurrentDBDocument

if not isinstance(template_docs, list):
    template_docs = [template_docs]

report.append("Template Docs = " + str(template_docs))

for doc in template_docs:
    template_file_path = FilePath(doc)
    
    current_doc_index = template_docs.index(doc)
    report.append("Current Document index from input template list is " + str(current_doc_index))
    associated_project_doc = project_docs[current_doc_index]
    report.append("Associated project file save name = " + str(associated_project_doc))
    project_template_path = FilePath(associated_project_doc)

    typelist = list()
    try:
        typelist.append(app.OpenDocumentFile(template_file_path, open_options))
        report.append("typelist variable = " + str(typelist))
        try:
            for tl in typelist:
                #Start Transaction
                TransactionManager.Instance.EnsureInTransaction(tl)
                collector = FilteredElementCollector(tl).OfCategory(BuiltInCategory.OST_ProjectInformation).ToElements()
                for col in collector:
                    report.append("For collector name " + str(col.Name))
                    for param in params_to_modify:
                        param_index = params_to_modify.index(param)
                        assoc_param_value = new_param_values[param_index]
                        if col.Name == param:
                            col.Set(assoc_param_value)
                            report.append("Current param " + str(param) + " at index " + str(param_index))
                            TransactionManager.Instance.TransactionTaskDone()
                        else:
                            report.append("Project Information parameter " + str(col.Name) + " did not match any parameters to modify")
        except:
            report.append("Error during transaction")
        try:
            typelist.SaveAs(project_template_path, saveas_options)
        except:
            report.append("Error during saving")
    except:
        report.append("Error when opening the document")
    
#List of Node Outputs
OUT = report

I started over. Found this forum post with a good answer by stillgotme. Code now opens, saveas, and closes with the desired options. Now I’m moving on to changing project parameter values.

Revised code is below:

#Node Inputs
template_docs = IN[0]
project_docs = IN[1]
params_to_modify = IN[2]
new_param_values = IN[3]

#Variables
output = []

open_options = OpenOptions()
open_options.Audit = False
open_options.DetachFromCentralOption = DetachFromCentralOption.DetachAndPreserveWorksets

worksharing_options = WorksharingSaveAsOptions()
worksharing_options.SaveAsCentral = True

save_options = SaveAsOptions()
save_options.MaximumBackups = 50
save_options.SetWorksharingOptions(worksharing_options)
save_options.OverwriteExistingFile = True
save_options.Compact = True

report = []

TransactionManager.Instance.ForceCloseTransaction()    

if IN[0]:
	try:
		for template_path_str in template_docs:
			#Get the save file path/name that is associated with the current template path/name
			current_doc_index = template_docs.index(template_path_str)
			project_path_str = project_docs[current_doc_index]
			#Convert trings to model paths
			template_model_path = FilePath(template_path_str)
			project_model_path = FilePath(project_path_str)
			#Open file
			newdoc = app.OpenDocumentFile(template_model_path, open_options)

			#Update ProjectInfo parameter values
			

			#SaveAs and Close file
			newdoc.SaveAs(project_model_path, save_options)
			newdoc.Close(True)

	except Exception, e:
		report.append("Exception at Open/Save Level : " + str(e))
else:
	report.append("Please attach the list of template paths to the IN[0]")
1 Like

I’m making progress. The script now updates a list of input project information parameters with a list of input values. Thanks to this forum post and troygates. i did have to use ForceCloseTransaction in lieu of TransactionTaskDone(). I’m not certain why but using TaskDone put up an error stating it could not close all open transactions. Code below:

if IN[0]:
	try:
		for template_path_str in template_docs:
			#Get the save file path/name that is associated with the current template path/name
			current_doc_index = template_docs.index(template_path_str)
			project_path_str = project_docs[current_doc_index]
			#Convert trings to model paths
			template_model_path = FilePath(template_path_str)
			project_model_path = FilePath(project_path_str)
			#Open file
			newdoc = app.OpenDocumentFile(template_model_path, open_options)

			#Update ProjectInfo parameter values
			TransactionManager.Instance.EnsureInTransaction(newdoc)
			try:
				for param in params_to_modify:
					#Get the parameter value that is associated with the current parameter in the parameters to modify list
					current_param_index = params_to_modify.index(param)
					param_value = new_param_values[current_param_index]
					#Lookup the old parameter value and update for the new parameter value
					lookup_param_og = newdoc.ProjectInformation.LookupParameter(param).AsString()
					newdoc.ProjectInformation.LookupParameter(param).Set(param_value)
					lookup_param_new = newdoc.ProjectInformation.LookupParameter(param).AsString()
					report.append("For Parameter: " + str(param) + " the old value was " + str(lookup_param_og) + " and the new value is " + str(lookup_param_new))
			except Exception, e:
				report.append("Exception trying to get/set parameters : " + str(e))
			TransactionManager.Instance.ForceCloseTransaction()

			#SaveAs and Close file
			newdoc.SaveAs(project_model_path, save_options)
			newdoc.Close(True)

	except Exception, e:
		report.append("Exception trying to open/save/close : " + str(e))
else:
	report.append("Please attach the list of template paths to the IN[0]")

When using transactions with documents other than the primary document, I have had better luck creating transactions from scratch.

t = Transaction(newdoc, 'Modify Project Information')
t.Start()
try:
    # Do something
except:
    t.RollBack()
else:
    t.Commit()
1 Like

Thanks! I’ll give a shot. I think I have the reload revit links part nearly complete but I’m running in to another ‘unable to close all open transaction phases!’ again.

Do I need to make a new transaction for each change e.g (one for modifying project info), (one for reloading links) etc…?

You can still use a single transaction per document–I just put ‘Modify Project Information’ as the transaction’s name as an example. If you weren’t closing the documents and did use multiple transactions it would allow you to undo certain parts of your script, but it doesn’t apply in this particular use case.

Well I’m trying it a few different ways but I keep running into the unable to close all open transactions error during the Revit Link Update. I ran through the code line by line and the line that causes the transaction error is:

rvt_link_type.LoadFrom(reload_path, wrkset_config)

I’ll keep working on it but if anyone has any thoughts below is the full code as I’ve modified it a bit since the last post:

#Node Inputs
template_docs = IN[0]
project_docs = IN[1]
params_to_modify = IN[2]
new_param_values = IN[3]

#Variables
open_options = OpenOptions()
open_options.Audit = False
open_options.DetachFromCentralOption = DetachFromCentralOption.DetachAndPreserveWorksets

worksharing_options = WorksharingSaveAsOptions()
worksharing_options.SaveAsCentral = True

save_options = SaveAsOptions()
save_options.MaximumBackups = 50
save_options.SetWorksharingOptions(worksharing_options)
save_options.OverwriteExistingFile = True
save_options.Compact = True

report = []

#TransactionManager.Instance.ForceCloseTransaction()    

if IN[0]:
	try:
		for template_path_str in template_docs:
			#Get the save file path/name that is associated with the current template path/name
			current_doc_index = template_docs.index(template_path_str)
			project_path_str = project_docs[current_doc_index]

			#Convert strings to model paths
			template_model_path = FilePath(template_path_str)
			project_model_path = FilePath(project_path_str)

			#Open file
			newdoc = app.OpenDocumentFile(template_model_path, open_options)
			report.append(str(newdoc.Title) + " = Current Document")
			
			#Generate a string list of the file names of the template files
			try:
				template_name_list = []
				for item in template_docs:
					split_list = []
					split_list += item.split("\\")
					last_item = split_list[len(split_list)-1]
					template_name_list.append(last_item)
				#report.append("temaplate name list contains : " + str(template_name_list))
			except Exception, e:
				report.append("Exception while creating list of template file names : " + str(e))

			#Update ProjectInfo parameter values
			#TransactionManager.Instance.EnsureInTransaction(newdoc)
			t1 = Transaction(newdoc, "Modify Project Information")
			t1.Start()
			try:
				param_update_bool = False
				for param in params_to_modify:
					#Get the parameter value that is associated with the current parameter in the parameters to modify list
					current_param_index = params_to_modify.index(param)
					param_value = new_param_values[current_param_index]
					#Lookup the old parameter value and update for the new parameter value
					lookup_param_og = newdoc.ProjectInformation.LookupParameter(param).AsString()
					if newdoc.ProjectInformation.LookupParameter(param):
						newdoc.ProjectInformation.LookupParameter(param).Set(param_value)
						param_update_bool = True
					lookup_param_new = newdoc.ProjectInformation.LookupParameter(param).AsString()
					#report.append("In document : " + str(newdoc) + " for Parameter: " + str(param) + " : old value was " + str(lookup_param_og) + " : new value is " + str(lookup_param_new))
			except Exception, e:
				report.append("Exception trying to get/set parameters : " + str(e))
				t1.RollBack()
			else:
				if param_update_bool:
					report.append("ProjectInfo parameters MODIFIED")
				else:
					report.append("NO ProjectInfo params were modified")
				t1.Commit()
			#TransactionManager.Instance.ForceCloseTransaction()

			#Update Revit Links
			#TransactionManager.Instance.EnsureInTransaction(newdoc)
			t2 = Transaction(newdoc, "Update Revit Links")
			t2.Start()
			try:
				revit_link_instances = FilteredElementCollector(newdoc).OfCategory(BuiltInCategory.OST_RvtLinks).WhereElementIsNotElementType().ToElements()
				#report.append("Contents of Revit Link Collector : " + str(revit_link_collector))
				rvt_links_update_bool = False
				revit_link_list = []
				for link in revit_link_instances:
					revit_link_list.append(link.Name)
				#report.append("list of revit links :" + str(revit_link_list))
				for link in revit_link_instances:
					for link_name in revit_link_list:
						for t_name in template_name_list:
							if link_name.find(t_name) != -1:
								report.append(str(link_name) + " MATCHED " + str(t_name))
								index_num = template_name_list.index(t_name)
								reload_path = FilePath(project_docs[index_num])
								wrkset_options = WorksetConfigurationOption()
								wrkset_options.CloseAllWorksets
								wrkset_config = WorksetConfiguration(wrkset_options)
								rvt_link_type = newdoc.GetElement(link.GetTypeId())
								rvt_link_type.LoadFrom(reload_path, wrkset_config)
								break
							# else:
							# 	report.append(str(link_name) + " didn't match any template names")
			except Exception, e:
				report.append("Exception while updating Revit links : " + str(e))
				t2.RollBack()
			#TransactionManager.Instance.ForceCloseTransaction()
			else:
				if rvt_links_update_bool:
					report.append("Revit Links UPDATED")
				else:
					report.append("NO revit Links were updated")
				t2.Commit()
			#SaveAs and Close file
			newdoc.SaveAs(project_model_path, save_options)
			newdoc.Close(True)
	except Exception, e:
		report.append("Exception trying to open/save/close : " + str(e))
else:
	report.append("Please attach the list of template paths to the IN[0]")


#Node Output
OUT = report

I’m thinking the issue is with my for loops and how I’m using them and not so much with how I’m using transactions. I’m gong to look for a better way to use the loops to get the data I need to update the links.

If any one can take a look at the above code under the comment #Update Revit Links and has suggestion please leave it.

I figured out the reloading of the links does not need a transaction. I moved the transaction close to after I update the parameters and before I begin to update the Revit Links. That does the trick. Now i’m cleaning up how I reload the links. Not certain it’s working how I want. I’ll post code when I get it figured out.

1 Like

So this cleaned up nicely I think. We use a three digit number at the front of all our revit files to designate the discipline. I made a list of the 3 digit numbers for the project file names that will be used to reload the template file names to. I then checked to see if the 3 digit number in the template link name matched one of the 3 digit numbers in the project file name. When it does, it grabs the index of the project file name and pulls that path. Then I just reload the file from the matching path. Code below:

#Generate a string list of the file names of the project files
			try:
				proj_name_list = []
				for item in project_docs:
					split_list = []
					split_list += item.split("\\")
					last_item = split_list[len(split_list)-1]
					proj_name_list.append(last_item.split("_")[0])
				#report.append("project name list contains : " + str(proj_name_list))
			except Exception, e:
				report.append("EXCEPTION creating list of PROJECT FILE NAMES : " + str(e))

#Update Revit Links
			try:
				rlinks_update_bool = False
				reloaded_links = []
				rlink_instances = FilteredElementCollector(newdoc).OfClass(RevitLinkInstance).ToElements()
				rlink_instance_names = []
				for link in rlink_instances:
					name = link.Name
					link_name = name.split("_")[0]
					rlink_instance_names.append(link_name)
					link_bool = False
					proj_path = ""
					for proj in proj_name_list:
						if link_name == proj:
							proj_index = proj_name_list.index(proj)
							proj_path = ModelPathUtils.ConvertUserVisiblePathToModelPath(project_docs[proj_index])
							link_bool = True
					if link_bool:
						rvt_link = newdoc.GetElement(link.GetTypeId())
						rvt_link.LoadFrom(proj_path, None)
						reloaded_links.append(link.Name.split(" : ")[0] + " RELOADED")
						rlinks_update_bool = True
				#report.append("rlink instance names = " + str(rlink_instance_names))			
				
			except Exception, e:
				report.append("EXCEPTION updating REVIT LINKS : " + str(e))
			else:
				if rlinks_update_bool:
					report.append("Revit Links UPDATED")
					report.append(reloaded_links)
				else:
					report.append("NO Revit Links were updated")

@Bennett, are you claiming victory on your task? Is your final code a compilation of the ones above or is it just the last one?

This sounds slightly familiar to something I was trying to set up for project setup as well. Are you setting up MEP models from an architectural model?

@JustinStirling, I am claiming victory over code that saves new central models from a collection of ‘template’ central models that are linked in to each other, updating a collection of project information parameters within each of those models, and reloading the links in each central model to be the new saved central model files.

Now I’m going to setup the UI to get all the inputs needed, and then I should have a working Dynamo graph.

I work in a multi-disciplinary firm (Arch, Civil, Strl, and MEP). For our new projects we setup the Arch, Strl, MP, and E central models simultaneously. This script aims to do that. I’ll clean up my final script and post it in totality.

The last piece of the puzzle is changing the worksets in the central models to be non-editable. From what I can tell the Revit API doesn’t allow you to do that. I would think you would have to change the Workset Class property IsEditable to be False but that property appears to be read only. Of course, I’m an architect, not a programmer, so I could be not realizing how to do that within Python in Dynamo.

2 Likes

@Bennett Bennett Thanks for sharing your endeavors. I am just getting started on a similar task but as an addin, so as usual I start with dynamo to test the idea for a sanity check. Hope you were able to solve you worksets editability

Hi @S.A.M Unfortunately I was not able to solve the workset editability. For now, the person creating the new project has to open the file and change the edibility of the worksets manually… If you find something helpful in your efforts please share.

Why not just enable worksets from a standard template file instead?
This would be much faster and a lot less trouble :slight_smile:

Sorry if the screen capture is in french, most script are mutli language.