How to link Revit files as cloud file path type directly with Dynamo?

Revit Link Path Type set as relative but not Found with any other user,

When I link Revit files stored in Autodesk Desktop Connector with Dynamo python script, by default the links path type is absolute, and manually I have to change all of them to relative path type, but when file is opened by any other user or computer, the links are not found even than the path that I read as relative is correct, then if I click one by one reload from…and the files are in Autodesk Desktop Connector drive in my computer, then the path type is called Cloud and cannot be modified which is what I want to get directly when linking files.

After changing path type to relative and opening in other computer:

Finally what I want to get from link Paths, then this can be opened correctly in any other computer:

The ability to programmatically create links to cloud documents was added in 2022 if I am not mistaken.

What Revit build are you using and what does your graph / code currently look like?

Revit 2021, not going to upgrade to Revit 2022 in years so why not updating Revit 2021 as hotfix.

import clr

if IN[3] != None :
	clr.AddReference("System")
	from System.Collections.Generic import List

# Import ToDSType(bool) extension method
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)

# Import DocumentManager and TransactionManager
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
from System.Collections.Generic import *

# Import RevitAPI
clr.AddReference("RevitAPI")
import Autodesk
from Autodesk.Revit.DB import *

import sys
pyt_path = r'C:\Program Files (x86)\IronPython 2.7\Lib'
sys.path.append(pyt_path)
import System

doc = DocumentManager.Instance.CurrentDBDocument

# Start Transaction
TransactionManager.Instance.EnsureInTransaction(doc)

if isinstance(IN[0], list) : in_links = IN[0] 
else : in_links = [IN[0]]

if isinstance(IN[1], list) : in_ws = IN[1] 
else : in_ws = [IN[1]]

pinned = IN[2]

if IN[3] != None :
	if isinstance(IN[3], list) : closedwsnames = IN[3] 
	else : closedwsnames = [IN[3]]
else : closedwsnames = None

# number of elements
try : numlinks = len(in_links)
except : OUT = 'Input a filepath'
	
try : numworksets = len(in_ws)
except : numworksets = 0

#check list lengths
if numlinks == numworksets : listlength = True
else : listlength = False

def linkmodel (fpath, doc, pin) :
	# Create the Revit Link Type
	mpu = ModelPathUtils.ConvertUserVisiblePathToModelPath(fpath)
	
	if closedwsnames == None :
		lnkoptions = RevitLinkOptions("")
	
	else :
		#Workset configuration
		try :
			worksets = WorksharingUtils.GetUserWorksetInfo(mpu)
			ows = []
			
			for ws in worksets :
				name = ws.Name
				wsid = ws.Id
				closed = []	
				for wsn in closedwsnames :
					if wsn in name :
						closed.append(wsid)
				if wsid not in closed :
					ows.append(wsid)
				
			#The workset Ids collected put in an IList format			
			openwsids = List[WorksetId](ows)			
			
			#Directly creating the RevitLinkOptions raises an error, so I create it once, and then extract the Worksetconfiguration to modify it. Had some issues with the Close() method of the WorksetConfiguration, Open() worked instead
			wc = WorksetConfiguration(WorksetConfigurationOption.CloseAllWorksets)
			lnkoptions = RevitLinkOptions(False, wc)
			wc2 = lnkoptions.GetWorksetConfiguration()
			wc2.Open(openwsids)
			lnkoptions = RevitLinkOptions(False, wc2)
			
		except :
			lnkoptions = RevitLinkOptions("")
			
			
	#Create RevitLinkType
	loadedLnkType = RevitLinkType.Create(doc, mpu, lnkoptions)
	
	# Create the Revit Link Instance
	lnkInstance = RevitLinkInstance.Create(doc, loadedLnkType.ElementId, ImportPlacement.Shared)
	
	#Pin link
	lnkInstance.Pinned = pin

	return lnkInstance

try:
	# Number of worksets input and filepaths input are different
	if IN[1] and listlength == False :
		OUT = "The number of worksets doesn't match the number of links"
		
		
	# Multiple Worksets input
	elif IN[1] and listlength == True  :
	
		#Get WorksetTable and current workset
		wstable = doc.GetWorksetTable()
		activewsid = wstable.GetActiveWorksetId()
		
		#Create list for output
		links = []
		
		for fpath, ws in zip(in_links, in_ws) :
		
			# Get WorksetId
			wssimpleid = ws.Id
			wsid = WorksetId(wssimpleid)
				
			# Set the workset
			WorksetTable.SetActiveWorksetId(wstable, wsid)
			
			# Create the Revit Link Type and Link Instance
			a = linkmodel(fpath, doc, pinned)
			
			#add created link to output
			links.append(a)
							
		#reset current workset	
		WorksetTable.SetActiveWorksetId(wstable, activewsid)
		
		#output
		OUT = links	

	# No worksets input
	else :
		links = []
		for fpath in in_links :
			a = linkmodel(fpath, doc, pinned)
			links.append(a)
		OUT=links
	
except:
    # if error accurs anywhere in the process catch it
    import traceback
    errorReport = traceback.format_exc()
    OUT = errorReport 

# End Transaction
TransactionManager.Instance.TransactionTaskDone()


Usually it is because it isn’t as simple as adding the feature, but involves enough changes to the codebase that when done you would have most of the new build but without having any tests run on it so it wouldn’t be stable for other core aspects (say, loading the homepage wouldn’t work but you would be able to programmatically set links) and tie up a development team for a significant chunk of the following release’s development cycle. As a rule if stuff can be backported to old builds without (1) breaking the software, (2) effectively rebuilding the entire code base so it is the new build, or (3) tying up a team long enough that work for the next release isn’t done, it will be added.

I’ll dig into options later, but asking on the Revit API forum is likely a better route as this is very code focused and entails a high degree of familiarity with a lot of rarely used classes.

1 Like

well, I am sure there is a workaround or other API task for converting to cloud link path, but little experience on that as BIM360 is something new that it does not seem to be high demanded

Though I would be very happy to be proven wrong, I believe that this indicates that some forge processing will be required to get links to map to the GUIDs needed to create cloud paths, and even then I am not sure that setting the value without the flag will give you the desired result, as reading the remarks around ‘file path’ in the 2022 and 2021.1 APIs you’ll see there is a specific call out to ‘server, network, or cloud path’ while 2021.1 only indicates server.

this is the answer of Autodesk:

Yes - I’m aware of that. Note that it is built using Revit 2022 as the final file type.

does this part of the code corresponds only to Revit 2022? I think this does the trick but I am not pretend to be expert in python using Dynamo.

ModelPathUtils.ConvertCloudGUIDsToCloudPath("US", Guid.Parse(guids[0]), Guid.Parse(guids[1]));

I understand a file in BIM360 it is defined by a project ID, Model ID, file name, and some JSON code definition

Yes, this code was not possible to build until the API changes in 2022 which I previously highlighted, though if you’d like you can confirm with the code author directly on the GitHub (submit an issue or reach out to them via their GitHub profile).

This solution also requires Forge to parse and store GUIDs for models, so a pure Python solution won’t fit here - you’ll need C# and web API skills to build that specific solution, and likely any full scale solution would need such tools or a lot more - round about effort to get the necessary information, such as journal parsing, collaboration cache review. Those may or may not provide the needed info (the GUIDs but may not align), but I believe it would take significantly longer as you’d have to upload all models to BIM360, then open all models locally with the local links, then close Revit, then parse the data, then repath the links (downloading each model again in the process of the repathing…). A full Forge app (via the methods shown and design collaboration) which just does all of the work is likely the best bet speed wise though.

This information could be retrieved from the temporary folder cache of the cloud worksharing files in the local drive of the machine used I guess

THe question is - are those values the same, or different based on the need for security? I haven’t tested. Remember you’d have to upload, then open, then parse the cache for all models… at some point your time investment to learn the code calls, build, test, and implement the solution will surpass the time to ‘do it manually for this job’, so be sure the scope of ‘just doing it manually’ is ahead of you as you work.

1 Like