Dynamo API - Set User Packages Location

Hi all,

I’m trying to build a Dynamo ViewExtension to automatically read and set a user’s package path locations, to ensure all users will have our central library of definitions loaded when they start Dynamo. We are anticipating users might need to hot-desk in the near future, so setting this via their DynamoSettings.xml file in AppData isn’t going to work. We’ve also had no success with robocopying this file using a login script.

Therefore, I’ve decided the best thing to do is to auto-deploy a ViewExtension to automatically set the package paths to the desired location. The ViewExtension works pretty well, with one minor caveat. Below is my code:

public class MyViewExtension : IViewExtension
{
    public void Dispose() { }
    public void Startup(ViewStartupParams startupParameters) { }
    public void Loaded(ViewLoadedParams loadedParameters)
    {
        StartupParams startupParams = loadedParameters.StartupParams;
        string defaultPackagesPath = @"\\OurOfficeNetworkPath\Packages";
        List<string> packagePaths = loadedParameters.StartupParams.PathManager.PackagesDirectories.ToList();
        if (!packagePaths.Contains(defaultPackagesPath))
        {
            if (!System.IO.Directory.Exists(defaultPackagesPath)) return;
            startupParams.PathManager.AddResolutionPath(defaultPackagesPath);
            startupParams.PathManager.ResolveLibraryPath(ref defaultPackagesPath);
            startupParams.Preferences.CustomPackageFolders.Add(defaultPackagesPath);
        }
    }
    public void Shutdown() { }
    public string UniqueId { get => Guid.NewGuid().ToString(); }
    public string Name { get => "Dynamo Plugin"; }
}

I expect there’s some redundancy in the above code. It successfully adds the package path to the user’s package paths list if it isn’t already there. However, it doesn’t actually load in anything. I still have to manually open up the dialog and click ‘Accept Changes’ to load in the packages. Is there an API call I’m missing? Am I using something wrong? @Michael_Kirschner2 @Qilong_Tang @Aparajit_Pratap

Thanks in advance for your help!
Ollie

I think you might have to do this via the PackageLoader.

2 Likes

Hey @john_pierson, I’ve had a go but not quite figured out how to use this, or which method to call.

I can set the package paths, which works well enough when I close and reopen Revit. But setting the package paths for the active session won’t visibly load in any of the packages. I think there must be another method somewhere that gets called when I press ‘Accept Changes’.

Hmm, a while back I was experimenting with the LoadNodeLibrary method. It looks like Konrad has done some of that too here, https://github.com/ksobon/archilab/blob/89549d00a5571f7e6f0475020462cfad46505399/archilabExtension/ArchilabExtension.cs#L52

1 Like

I saw this one - it looked to me like it only takes paths to .dlls?
So it would work with some packages :slight_smile:

1 Like

Ah yes. I didn’t really think about that. So with DLLs but that is about it. Would be keen to see if @Michael_Kirschner2 or @solamour have some insight for non-DLL packages.

1 Like

You should be able to get the PackgeMangerExtension from the ExensionsManager - then get the PackageLoader - there will be some public methods for loading packages there.

Be careful though - you need to write your code defensively as extensions may be missing at runtime - for example, if for some reason the package manager extension is not loaded your extension may crash if it depends on it existing. Use try/catch, reflection, etc.

4 Likes

Hi Michael, I’m getting closer based on what you said. I can find the PackageLoader on the Github (https://github.com/DynamoDS/Dynamo/blob/717bb1649acb4bb6c14509b09e3921d2ce54c548/src/DynamoPackages/PackageLoader.cs) but I’m unable to access it via Intellisense. Can you please advise?:

using System;
using System.Collections.Generic;
using System.Linq;
using Dynamo.Extensions;
using Dynamo.Wpf.Extensions;
namespace AHMMViewExtension
{
    public class AHMMViewExtension : IViewExtension
    {
        public void Dispose() { }
        public void Startup(ViewStartupParams startupParameters)
        {
            List<IExtension> extensions = startupParameters.ExtensionManager.Extensions.ToList();
            for (int i = 0; i < extensions.Count; i++)
            {
                IExtension extension = extensions[i];
                if(extension.Name == "DynamoPackageManager")
                {
                    var packageManagerExtension = extension;
                }
            }
        }
        public void Loaded(ViewLoadedParams loadedParameters) { }
        public void Shutdown() { }
        public string UniqueId { get => Guid.NewGuid().ToString(); }
        public string Name { get => "AHMM Dynamo Plugin"; }
    }
}

afaik the extension .dll is not shipped with nuget packages. You should reference it from your Dynamo Runtime folder (I think is DynamoPackages.dll) and make sure not to copy it to the package bin folder.

2 Likes

Spot on @alvpickmans , thank you for the tip! :smiley:

    public void Startup(ViewStartupParams startupParameters)
    {
        PackageManagerExtension GetPackageManagerExtension()
        {
            List<IExtension> extensions = startupParameters.ExtensionManager.Extensions.ToList();
            return extensions.FirstOrDefault(x => x.Name == "DynamoPackageManager") as PackageManagerExtension;
        }

        const string defaultPackagesPath = @"\\...ExamplePackagesPath\Packages";
        List<string> foundPackagePaths = startupParameters.Preferences.CustomPackageFolders;

        if (!foundPackagePaths.Contains(defaultPackagesPath))
        {
            if (!System.IO.Directory.Exists(defaultPackagesPath)) { return; }
            startupParameters.Preferences.CustomPackageFolders.Add(defaultPackagesPath);
            PackageManagerExtension packageManagerExtension = GetPackageManagerExtension();
            LoadPackageParams loadPackageParams = new LoadPackageParams();
            if (loadPackageParams.Preferences == null)
            {
                loadPackageParams.Preferences = startupParameters.Preferences;
            }
            if (loadPackageParams.PathManager == null)
            {
                loadPackageParams.PathManager = startupParameters.PathManager;
            }
            packageManagerExtension.PackageLoader.LoadAll(loadPackageParams);
        }
    }  

I seem to be getting a silent error when using PackageLoader.LoadAll(). Am I doing something obviously wrong?
I can’t seem to find a way to debug Dynamo, since the process doesn’t exist for me to attach to. Is there a way to debug this like a Revit addin?

just attach to Revit.

1 Like

yes… treating the extensions like APIs which will always exist is problematic…

Sorry to be a PITA, but while I can attach, I can’t seem to find where LoadAll() is failing. I can see inside the relevant method bodies on Github, I can’t attach a breakpoint or step through in VS.

Given that we (likely others, too) will see an increase in hot-desking, we think this code might be useful to many other practices - if we can get it working. Is there something obviously wrong with what I’m passing to the LoadAll() method?

use this to break programmatically in your code before you call the api, also turn on all exceptions in exceptions settings - what you want to do is to break on exceptions, even handled ones.

1 Like

agree, but sometimes you just have to work with the tools at hand :slight_smile:
For this case an adapter pattern or wrapper would minimise issues on API changes in the future

Sorry Michael, I didn’t figure out how to use Debugger.Break as you suggested.

I tried using DotPeek to generate a .pdb to let me debug your LoadAll method, but I couldn’t get it working. The method fails silently, so unless I can figure out where the code fails, I won’t be able to adjust / fix it.

that method will break the debugger at the line of code where you place it - (if you are attached to Revit) - then you can enable all exceptions to be caught in exception settings and checkout what exception is raised inside of Dynamo’s code.

The way you initialize the loadedPackageParams looks odd, look for how it is initialized in dynamo’s source.

@oliver.green The below seems to work for me. It adds the new path to the Custom Folder, then loads the packages for any “pkg.json” files it finds.

        string NewPackageLocation = "C:\\Temp2";

        List<IExtension> extensions = startupParams.ExtensionManager.Extensions.ToList();
        PackageManagerExtension dynamoPackageManager = extensions.FirstOrDefault(x => x.Name == "DynamoPackageManager") as PackageManagerExtension;

        List<string> CurrentPackagePaths = startupParams.Preferences.CustomPackageFolders;

        if (!CurrentPackagePaths.Contains(NewPackageLocation) && System.IO.Directory.Exists(NewPackageLocation))
        {
            startupParams.Preferences.CustomPackageFolders.Add(NewPackageLocation);

            foreach (string PkgFile in Directory.GetFiles(NewPackageLocation, "pkg.json", SearchOption.AllDirectories))
            {
                dynamoPackageManager.PackageLoader.Load(dynamoPackageManager.PackageLoader.ScanPackageDirectory(PkgFile.Replace("pkg.json", "")));
            }
        }
2 Likes

Further to my above post, this is a video of it working with a slight tweak to work with a setting file.

https://autode.sk/2OCaAwW

1 Like