ACC Desktop Connector Path - Is there a better way?

Is there no good way to get document paths to the local Desktop connector? we could use a better way to properly get these paths to the Current ACC project, and the current document path (with subfolders)

Tracking back through getting Desktop Connector (DC) paths - ended up in a Zero Touch(Below) but might drop back to Python if we’re just parsing the DC path. I want our tools to write out JSON files when team run the apps to track what happened, when, and how much they are being used (Net to the RVT file, the project and a central location- maybe a SQL DB).

The top output of the ZTN is the complete DC Path to the model less the RVT extension to quick-tac on the extension and write out the fie.

//Revit API namespaces
using Autodesk.Revit.DB;
using Autodesk.Revit.ApplicationServices;
using Autodesk.DesignScript.Runtime;
//using RevitServices.Persistence;

//using System;
using System.Collections.Generic;
using System.Linq;

namespace BETA
{
    public static partial class Parameters
    {
        // Expose this method to Dynamo with named return values
        [IsVisibleInDynamoLibrary(true)]
        [MultiReturn(new[] { "SharedParamMatch", "SharedParamMismatch", "ProjectParam", "Debug" })]
        public static Dictionary<string, List<string>> ParametersByType(object docInput, string sharedParamFilePath)
        {
            // Lists to store categorized parameter names
            var sharedParamMatch = new List<string>();       // Parameters found in both Revit and shared param file
            var sharedParamMismatch = new List<string>();    // Parameters only in shared param file
            var projectParam = new List<string>();           // Parameters only in Revit project
            var debug = new List<string>();                  // Step-by-step debug messages

            // Convert input to Revit document
            Document doc = RevitDocHelper.GetDocument(docInput);
            if (doc == null)
            {
                // Early exit if the document is invalid
                debug.Add("Invalid Revit document");
                return new Dictionary<string, List<string>> {
                    { "SharedParamMatch", sharedParamMatch },
                    { "SharedParamMismatch", sharedParamMismatch },
                    { "ProjectParam", projectParam },
                    { "Debug", debug }
                };
            }

            // Check for a valid shared parameter file path
            if (string.IsNullOrWhiteSpace(sharedParamFilePath))
            {
                debug.Add("Shared parameter file path is null or empty");
                return new Dictionary<string, List<string>> {
                    { "SharedParamMatch", sharedParamMatch },
                    { "SharedParamMismatch", sharedParamMismatch },
                    { "ProjectParam", projectParam },
                    { "Debug", debug }
                };
            }

            // Load the shared parameter file
            Application app = doc.Application;
            app.SharedParametersFilename = sharedParamFilePath;
            DefinitionFile defFile = app.OpenSharedParameterFile();
            if (defFile == null)
            {
                debug.Add("Failed to load shared parameter file at path");
                return new Dictionary<string, List<string>> {
                    { "SharedParamMatch", sharedParamMatch },
                    { "SharedParamMismatch", sharedParamMismatch },
                    { "ProjectParam", projectParam },
                    { "Debug", debug }
                };
            }

            // Set of lowercased shared parameter names for fast lookup
            var sharedParamNamesSet = new HashSet<string>();
            // Dictionary to retain original casing for later display
            var sharedParamNamesAll = new Dictionary<string, string>(); // key = lowercased name, value = original name

            // Iterate through all shared parameters in all groups
            foreach (var def in defFile.Groups.Cast<DefinitionGroup>().SelectMany(g => g.Definitions.Cast<Definition>()).OfType<ExternalDefinition>())
            {
                string name = def.Name?.Trim() ?? "";
                string lower = name.ToLowerInvariant();
                if (!string.IsNullOrEmpty(lower))
                {
                    sharedParamNamesSet.Add(lower);
                    if (!sharedParamNamesAll.ContainsKey(lower))
                        sharedParamNamesAll[lower] = name;
                }
            }

            debug.Add("Loaded " + sharedParamNamesAll.Count + " shared parameters");

            // Set to track shared parameters actually found in project
            var foundSharedParams = new HashSet<string>();
            // Track all project parameters for fallback comparison
            var allProjectParamNames = new HashSet<string>();

            // Revit's project parameter bindings
            BindingMap map = doc.ParameterBindings;
            DefinitionBindingMapIterator iter = map.ForwardIterator();
            iter.Reset();

            // First pass to check which shared params exist in project
            while (iter.MoveNext())
            {
                Definition def = iter.Key;
                string name = def?.Name?.Trim() ?? "Unnamed";
                string lowerName = name.ToLowerInvariant();

                // Determine binding type (Instance or Type)
                string bindingType = iter.Current is InstanceBinding ? "Instance" :
                                     iter.Current is TypeBinding ? "Type" : "Unknown";

                // Add to full set of project param names
                allProjectParamNames.Add(lowerName);

                // If parameter matches a shared param (by lowercase name)
                if (sharedParamNamesSet.Contains(lowerName))
                {
                    sharedParamMatch.Add(name);             // Add to match list (original name)
                    foundSharedParams.Add(lowerName);       // Track as found
                    debug.Add("Shared match " + name + " " + bindingType);  // Log match
                }
            }

            // Find shared parameters that are *not* found in project
            foreach (var kvp in sharedParamNamesAll)
            {
                if (!foundSharedParams.Contains(kvp.Key))
                {
                    sharedParamMismatch.Add(kvp.Value); // Use original casing
                    debug.Add("Shared mismatch " + kvp.Value);
                }
            }

            // Second pass to find parameters that are in project but not shared
            iter.Reset();
            while (iter.MoveNext())
            {
                Definition def = iter.Key;
                string name = def?.Name?.Trim() ?? "Unnamed";
                string lowerName = name.ToLowerInvariant();

                // Add to projectParam only if not already matched or found in shared param list
                if (!foundSharedParams.Contains(lowerName) && !sharedParamNamesSet.Contains(lowerName))
                {
                    projectParam.Add(name);
                    debug.Add("Project only " + name);
                }
            }

            // Return all three categories along with debug log
            return new Dictionary<string, List<string>> {
                { "SharedParamMatch", sharedParamMatch },
                { "SharedParamMismatch", sharedParamMismatch },
                { "ProjectParam", projectParam },
                { "Debug", debug }
            };
        }
    }
}

1 Like

There is the undocumented connector API typically located here C:\Program Files\Autodesk\Desktop Connector\Autodesk.DesktopConnector.API.dll

There is this repo GitHub - eirannejad/EasyADC: Easy Wrapper for Autodesk Desktop Connector API

However - I would use the ACC Data Management API first

Finally there are a few helpers and classes in the Revit API
ModelPathUtils, ModelPath and its sub-classes FilePath and ServerPath

1 Like

It sounds like you are going about fetching these after the fact, which may be the wrong way to go about it. More on that in a few.

The reality is that as soon as you move your data to any type of scaled cloud solution you need to use that tool’s APIs to track ‘what’ things are and where they are displayed. This is because the paths which you think of don’t exist. Everything is just a GUID in a GUID. There are no folders. There is no file name. There is no extension. Just two GUIDs. The file names and paths which you see in ACC are only representations of the metadata associated to the second GUID. That metadata is pulled via the API. The content you see in desktop connector, Revit, and anything else exploring the content in ACC is built to represent the folder structures we are familiar with, packed into place using the exact same Autodesk Platform Services (APS) API. So if you have the cloud model open in Revit and you want to fetch the “path” as you conceptualize it you are best off pulling the model GUID and project GUID and then making a API call with APS to fetch the rest of the info. Yes this means you’ll need to do a few API calls to the APS service to get the data you want.

But as noted before it is likely there is a better way to fetch this info. Some things to consider:

  1. Dynamo for Revit interacts with the active document. Dynamo for Revit always knows which document it is associated to. It is built into the model used by Dynamo to execute the graphs. This means Dynamo has access to ‘what file’ it is actively associated to at any moment.
  2. Dynamo has a authentication mechanism built into it as well. This means you have a way to confirming ‘who’ from any Dynamo context as well, and if you prefer you can always grab the active windows username just as well.
  3. Dynamo’s execution engine tracks warnings, errors, infostates, execution start, execution completion, and object count at every node. This means you can also identify warnings, errors, info states, execution duration, modified object counts, and more.
  4. The Dynamo API calls to attain all of the info in #1-3 above is not really documented the way you might hope, but they are there and can be used as an extension of Dynamo, meaning all runs can be tracked even if no ‘logger’ node is present on the canvas. It can also be triggered via Dynamo Player.
  5. The Desktop connector API is there and can be used at your own risk. However it is private and as such may change at any given update - what works this week might be blown into oblivion next. It is private for a reason - these are web services and those have costs to run, but the team wants that kept free so it is left closed. To prevent abuse or creating budget overruns or getting all of ACC locked due to AWS not scaling enough there are rate limits can be put in place. These don’t just track the user but projects and offices, so abuse can make everyone’s work crawl along at a near stop which isn’t something you want to do. Lastly use of such private APIs might go against the terms of service, so consult your legal business partner before you poke it. If all of that sounds like I am saying ‘this is a bad idea and could have serious repercussions that will haunt you for far after the fire that starts is put out’… well that is good. APS is the solution you want, not desktop connector.
  6. If you cannot get the ‘directory’ you need from the active document, logging the project and model GUIDs and building a lookup table for the unique values is going to both be faster (one call per model) and less expensive (cost and/or rate limiting) as you only check the graph which was run 9,000 times on model X due to the user leaving periodic run mode on for 2 and a half hours. Since you want to ‘report’ this and you don’t need the report every second of every day you can likely pull the unique GUIDs once a day and make one API call and use that as the lookup at any moment and never have anything other than ‘new today’ models not reporting correctly.
  7. For bulk background processing of models the logger needs to be written in with your open and close. There is no “Dynamo magic” which can be leveraged there like you can get with the Dynamo API. This is another reason to consider the Dynamo Multiplayer from Bird Tools as that should support the same extension for active document work.
5 Likes

Thanks : )

The path info serves several purposes:

  • Usage tracking & ROI - Identify who ran a Dynamo script on a given model and how often, so we can quantify impact (e.g., “Script X used Y times saved Z hours”) to justify ongoing development and maintenance.

  • Audit & accountability -Record script usage history for validation, QA, and team accountability.

  • Associated data retention - Save related artifacts (Ideate scripts, Dynamo definitions, exports) alongside the projects/models they were used with, ensuring context is never lost.

Example workflow under development:

  1. Proof of concept - Built a “code calculator” entirely in Revit schedules (painful, but avoided Ideate to prove feasibility).

  2. Excel + Ideate - Perform lookups/calcs in Excel via Ideate, push results back to Revit.

  3. Full add-in - Manage parameters and data via a dedicated Revit add-in with stored tables, pushing validated results directly to the model.
    Justification: Each step increases automation, reduces manual errors, and provides measurable time savings.

Sounds like there’s a better way to globally retrieve Dynamo runtimes, user data, and model IDs - or to target locations for storing related files & I’m open to exploring it.

I could use an APS tie-in from another app I am working towards for project setup automation. Data pulled in conjunction with this could be used as a reference file for quick access during audits or migrations