Launch an add-in from a PyRevit button

I want to deploy a plugin to all my office colleagues at once called Librarian.
I would like to move the necessary files (.xml and contents folder) inside a button folder because we are developing an internal Pyrevit tab full of scripts and utilities and running it from inside.

It’s possible?

Hi @poweredbygeorge , of course! there are many differente resources on the web. I advice you one of them:

From my understanding, I wrote this script.py:

# This line specifies the path to the DLL file of the Librarian add-in
__assembly__ = r'C:\ProgramData\Autodesk\ApplicationPlugins\Librarian.bundle\Contents\2024\Librarian.dll'

# This line specifies the class within the DLL that should be executed
__command_class__ = 'Librarian.LibrarianExternalApp'

# Optional metadata
__title__ = 'Librarian'
__author__ = 'spicetools'
__helpurl__ = 'https://spicetools.blogspot.com/p/librarian.html'

This file is located inside a folder called Librarian.linkbutton alongside a icon.png file.

Is it correct?

Librarian.invokebutton

and in a yaml file as stated in :point_up:

1 Like

Thanks for your reply!

So now I have a folder called Librarian.invokebutton that contains:

icon.png
bundle.yaml:

# This line specifies the path to the DLL file of the Librarian add-in
__assembly__ = r'C:\ProgramData\Autodesk\ApplicationPlugins\Librarian.bundle\Contents\2024\Librarian.dll'

# This line specifies the class within the DLL that should be executed
__command_class__ = 'Librarian.LibrarianExternalApp'

# Optional metadata
__title__ = 'Librarian'
__author__ = 'spicetools'
__helpurl__ = 'https://spicetools.blogspot.com/p/librarian.html'

The result is:

Screenshot 2023-11-22 161812

The add-in file contains:

<?xml version="1.0" encoding="utf-8" ?>
<RevitAddIns>
	<AddIn Type="Application">
		<Name>Librarian</Name>
		<Assembly>Librarian.dll</Assembly>
		<AddInId>86D1670D-8C46-462B-B8EC-146169796DD0</AddInId>
		<FullClassName>Librarian.LibrarianExternalApp</FullClassName>
		<VendorId>STPL</VendorId>
		<VendorDescription>SpiceTools Technologies Private Limited, www.spicetools.org</VendorDescription>
	</AddIn>
</RevitAddIns>

So I assume that my command class should be Librarian.LibrarianExternalApp, isn’t it?

It would make sense, indeed.
try LibrarianExternalApp only?

1 Like

After a fresh restart of Revit Pyrevit says:

Screenshot 2023-11-22 164229

This happen for both version of the command…Do you have any clues about that?

You will need to inspect the dll to figure out the appropriate class name

1 Like

Is there a way to inspect a dll file or should I contact the developer for this?

This will be your second-best friend :laughing:
https://www.jetbrains.com/fr-fr/decompiler/

1 Like

Ok, my Librarian.linkbutton seems to work now:

title: Test Link Button (Custom Title)
tooltip: Test Link Button Tooltip
author: "{{author}}"
highlight: new
assembly: 'C:\ProgramData\Autodesk\ApplicationPlugins\Librarian.bundle\Contents\2024\Librarian.dll'
command_class: LibrarianExternalApp

The fact is that when I try to run it (from pyrevit) the script returns me this error:

image

Adding inside my bundle.yaml this line of code:

availability_class: PyRevitTestLinkExternalCommandAvail

The error become:
image

Is there a way to solve this last (big?) step? Thanks!

I’m trying to change approach.

A simple pushbutton with this script.py inside could theoretically work?

from Autodesk.Revit.UI import RevitCommandId, PostableCommand, UIApplication
from pyrevit import script

# Function to execute the Librarian command
def execute_librarian_command():
    # Define the command name as found in the journal file
    command_name = "CustomCtrl_%CustomCtrl_%CustomCtrl_%Add-Ins%SPICETOOLS%RUTILITIES%Librarian:Librarian.ExternalCommands.CommandToggleDockVisibility"

    # Get the current Revit UIDocument and UIApplication
    uidoc = __revit__.ActiveUIDocument
    uiapp = __revit__.Application

    # Attempt to find the command by its name
    command_id = RevitCommandId.LookupCommandId(command_name)

    if command_id and uiapp.CanPostCommand(command_id):
        # If the command is found and can be posted, execute it
        uiapp.PostCommand(command_id)
    else:
        # If the command cannot be found or executed, print a message
        print("Command not found or not executable: " + command_name)

# Execute the function
execute_librarian_command()

It should if there is no mistake in the code.

command_id = DB.RevitCommandId.LookupCommandId("CustomCtrl_%CustomCtrl_%CustomCtrl_%Add-Ins%SPICETOOLS%RUTILITIES%Librarian:Librarian.ExternalCommands.CommandToggleDockVisibility")
uiapp.PostCommand(command_id.Id)
1 Like

What I need to import to make it work?

See uiapp property

from pyrevit import UI, HOST_APP

uiapp = HOST_APP.uiapp

command_id = UI.RevitCommandId.LookupCommandId("CustomCtrl_%CustomCtrl_%CustomCtrl_%Add-Ins%SPICETOOLS%RUTILITIES%Librarian:Librarian.ExternalCommands.CommandToggleDockVisibility")
uiapp.PostCommand(command_id.Id)
1 Like

Pyrevit says:

IronPython Traceback:
AttributeError: ‘Autodesk.Revit.DB’ object has no attribute ‘RevitCommandId’

Do you know if there exists a documented example?

I wasn’t in front of my computer at the time.
It should be

from pyrevit import HOST_APP, UI

uiapp = HOST_APP.uiapp

command_id = UI.RevitCommandId.LookupCommandId("CustomCtrl_%CustomCtrl_%CustomCtrl_%Add-Ins%SPICETOOLS%RUTILITIES%Librarian:Librarian.ExternalCommands.CommandToggleDockVisibility")
uiapp.PostCommand(command_id.Id)
1 Like

Thanks for your patience, I’m learning a lot.

from pyrevit import HOST_APP, UI

uiapp = HOST_APP.uiapp

command_id = UI.RevitCommandId.LookupCommandId(“CustomCtrl_%CustomCtrl_%CustomCtrl_%Add-Ins%SPICETOOLS%RUTILITIES%Librarian:Librarian.ExternalCommands.CommandToggleDockVisibility”)
uiapp.PostCommand(command_id.Id)

I got this error:

IronPython Traceback:
Traceback (most recent call last):
File “P:\GESTIONE PROGETTI PARK\BIM\24 pyRevit\Parkour_dev\Parkour_dev C24.extension\Parkour_dev.tab\Families.panel\Librarian.pushbutton\script.py”, line 6, in
AttributeError: ‘NoneType’ object has no attribute ‘Id’

Script Executor Traceback:
System.MissingMemberException: ‘NoneType’ object has no attribute ‘Id’
at Microsoft.Scripting.Interpreter.ThrowInstruction.Run(InterpretedFrame frame)
at Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)
at Microsoft.Scripting.Interpreter.LightLambda.Run3[T0,T1,T2,TRet](T0 arg0, T1 arg1, T2 arg2)
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at Microsoft.Scripting.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame)
at Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)
at Microsoft.Scripting.Interpreter.LightLambda.Run2[T0,T1,TRet](T0 arg0, T1 arg1)
at IronPython.Compiler.PythonScriptCode.RunWorker(CodeContext ctx)
at PyRevitLabs.PyRevit.Runtime.IronPythonEngine.Execute(ScriptRuntime& runtime)

TLDR; It won’t work with Librarian


partially true only :point_down:

It means this command does not have a CommandId as it is not found with the lookup function.
I checked installing the Librarian addin. The command name of what you are looking for is right but it is an external command that does not map to keyboard shortcuts, which is usually an indicator that it is not possible to call it through the PostCommand or find it with the LookupCommanId

1 Like

Hi! I just ended up with a working snippet.

Following your suggestion, I’ve just decompiled the DLL called from the addin to look at how the class was implemented inside.

The structure of the file is attached below:

So after some trial and error, this code seems to work fine:

bundle.yaml

title: Test Link Button (Custom Title)
tooltip: Test Link Button Tooltip
author: "{{author}}"
highlight: new
assembly: 'C:\ProgramData\Autodesk\ApplicationPlugins\Librarian.bundle\Contents\2023\Librarian.dll'
command_class: ExternalCommands.CommandToggleDockVisibility

Thanks for support,
Giorgio

1 Like