├── GLOSSARY.md
├── README.md
├── SUMMARY.md
├── accessing_specific_overloads_in_ironpython_documentload.md
├── book.json
├── collecting_elements
└── README.md
├── connecting_to_a_sqlite_database
└── README.md
├── creating_geometry
└── README.md
├── deploying_rpsaddins
├── README.md
├── creating_the_installer.md
├── creating_the_rpsaddin_manifest.md
├── what_happens_when_deploying_an_rpsaddin.md
└── what_is_a_rpsaddin.md
├── examples__cookbook.md
├── external_scripts.md
├── frequently_asked_questions
└── README.md
├── hello_world
└── README.md
├── how-to-write-in-the-url-field-of-a-family-with-the-revitpythonshell.md
├── installing_revitpythonshell_for_autodesk_revit
├── README.md
└── files_and_locations.md
├── ironpython_notes.md
├── limitations_of_the_non-modal_shell
└── README.md
├── more_control_over_the_ribbonpanel.md
├── predefined_variables
└── README.md
├── revitlookup_and_revitpythonshell.md
├── the_configure_dialog
├── 2015.02.27_Ribbon_Panel.png
├── 2015.03.18_Configure_RevitPythonShell.png
├── README.md
├── initScript-startupScript.png
├── initscript,_startupscript.md
├── search-dialog.png
├── search_paths.md
├── variables-0.png
├── variables-1.png
├── variables.md
└── variables.png
├── using_linq_in_ironpython
└── README.md
├── using_the_startupscript_to_modify_the_ribbonpanel
└── README.md
├── webserver_example
└── README.md
├── where_to_learn_python.md
└── where_to_learn_revit_api.md
/GLOSSARY.md:
--------------------------------------------------------------------------------
1 | ## external script
2 |
3 | A script defined as a file and added to the *Commands* tab.
4 |
5 | ## RpsAddin manifest
6 |
7 | An xml file describing the contents of an RpsAddin, used to build the RpsAddin dll with the "Deploy RpsAddin" feature.
8 |
9 | ## Revit addin manifest
10 |
11 | An xml file with the extension .addin, that tells Revit to load a plugin at startup. See the Revit API documentation for more information.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | The RevitPythonShell exposes the Revit API to the Python programming language. And to keep with the spirit of Python, it includes a REPL (Read Evaluate Print Loop), the interactive shell that let's you try out code snippets *inside* a running Autodesk Revit instance! This offers a totally new way to create addins for Revit and Vasari with a more hands-on experience.
2 |
3 | Features:
4 |
5 | * Python language scripting (IronPython)
6 | * Syntax highlighting
7 | * full access to the Revit API
8 | * export RevitPythonShell scripts as standalone-addins
9 | * access to the *batteries included* python standard library as well as the .NET framework - mix the best of both worlds
10 | * edit + run scripts without restarting Revit
11 |
12 |
13 | Brainstorming chapters / sections:
14 | * Callum: Ideas for chapters could include interface creation (I use winforms, but this opens a whole other realm - I find I spend more time coding the interface than I do manipulating the database!), and a whole lot of simple examples (geometry, revisions, sheet manipulation, family placement, family purging/renaming) etc.
15 | * mention places to learn about Revit API programming (The Building Coder)
16 | * [The Revit API Developers Guide](http://help.autodesk.com/cloudhelp/2015/ENU/Revit-API/files/GUID-F0A122E0-E556-4D0D-9D0F-7E72A9315A42.htm)
17 | * mention guides to convert c# code to IronPython
18 | * go through the Revit API examples, translating them
19 | * Contribute (tell people how to get in contact with bug fixes and new features from the community)
20 | * including python libraries
21 | * InitScript with src attribute
22 | * can we have multiple StartupScripts?
23 | * interactive non-modal REPL
24 | * sample scripts from the SDK? go through them one at a time
25 | * in fact: new goal: every friday, try out some new piece of API and blog it?
26 | * Close Revit from the API (in StartupScript e.g.)
27 |
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | * [Introduction](README.md)
4 | * [Where to learn Python](where_to_learn_python.md)
5 | * [Where to learn the Revit API](where_to_learn_revit_api.md)
6 | * [Installing RevitPythonShell for Autodesk Revit](installing_revitpythonshell_for_autodesk_revit/README.md)
7 | * [Files and Locations](installing_revitpythonshell_for_autodesk_revit/files_and_locations.md)
8 | * [The "Configure" dialog](the_configure_dialog/README.md)
9 | * [External scripts](external_scripts.md)
10 | * [Search Paths](the_configure_dialog/search_paths.md)
11 | * [InitScript, StartupScript](the_configure_dialog/initscript,_startupscript.md)
12 | * [Variables](variables.md)
13 | * [Predefined variables](predefined_variables/README.md)
14 | * [Hello World!](hello_world/README.md)
15 | * [Collecting Elements](collecting_elements/README.md)
16 | * [Creating Geometry](creating_geometry/README.md)
17 | * [Deploying RpsAddins](deploying_rpsaddins/README.md)
18 | * [What is a RpsAddin](deploying_rpsaddins/what_is_a_rpsaddin.md)
19 | * [What happens when deploying an RpsAddin](deploying_rpsaddins/what_happens_when_deploying_an_rpsaddin.md)
20 | * [Creating the RpsAddin manifest](deploying_rpsaddins/creating_the_rpsaddin_manifest.md)
21 | * [Creating the Installer](deploying_rpsaddins/creating_the_installer.md)
22 | * [Examples \/ Cookbook](examples__cookbook.md)
23 | * [Webserver example](webserver_example/README.md)
24 | * [More control over the RibbonPanel](more_control_over_the_ribbonpanel.md)
25 | * [Connecting to a SQLite database](connecting_to_a_sqlite_database/README.md)
26 | * [How to write in the URL field of a family with the RevitPythonShell](how-to-write-in-the-url-field-of-a-family-with-the-revitpythonshell.md)
27 | * Frequently Asked Questions
28 | * [Limitations of the non-modal Shell](limitations_of_the_non-modal_shell/README.md)
29 | * [Frequently Asked Questions](frequently_asked_questions/README.md)
30 | * [RevitLookup and RevitPythonShell](revitlookup_and_revitpythonshell.md)
31 | * [IronPython notes](ironpython_notes.md)
32 | * [Using Linq in IronPython](using_linq_in_ironpython/README.md)
33 | * [Accessing specific overloads in IronPython \(Document.Load\)](accessing_specific_overloads_in_ironpython_documentload.md)
34 |
35 |
--------------------------------------------------------------------------------
/accessing_specific_overloads_in_ironpython_documentload.md:
--------------------------------------------------------------------------------
1 | # Accessing specific overloads in IronPython (Document.Load)
2 |
3 | The RevitPythonShell makes working with Revit a bit easier, because, you know, Python. The specific flavour of Python used is IronPython - a port of the Python language to the .NET platform. IronPython lets you call into .NET objects seamlessly. In theory. Except when it doesen't. [All abstractions are leaky](http://www.joelonsoftware.com/articles/LeakyAbstractions.html).
4 |
5 | This article is all about a specific leak, based on an impedance mismatch between the object model used in .NET and that used in Python: In C#, you can [overload a method](https://msdn.microsoft.com/en-us/library/ms229029%28v=vs.110%29.aspx). A simple way to think about this is to realize that the name of the method includes its *signature*, the list of parameter (types) it takes. Go read a book if you want the gory details. Any book. I'm just going to get down to earth here and talk about a specific example:
6 |
7 | The [`Document.LoadFamily`](http://revitapisearch.com/html/2966229b-60b0-404d-5ffe-e4c4d85d2d7a.htm) method.
8 |
9 | The standard method for selecting a specific overload is just calling the function and having IronPython figure it out.
10 |
11 | I guess the place to read up on how to call overloaded methods is here: http://ironpython.net/documentation/dotnet/dotnet.html#method-overloads. To quote:
12 |
13 | > When IronPython code calls an overloaded method, IronPython tries to select one of the overloads at runtime based on the number and type of arguments passed to the method, and also names of any keyword arguments.
14 |
15 | This works really well if the types passed in match the signatures of a specific method overload well. IronPython will try to automatically convert types, but will fail with a `TypeError` if more than one method overload matches.
16 |
17 | The `Document.LoadFamily` method is special in that one of its parameters is marked as `out` in .NET - according to the standard IronPython documentation (REF) that should translate into a tuple of return values - and it does, if you know how. It is just non-intuitive - see [this question on Stack Overflow](http://stackoverflow.com/questions/31471089/how-to-pick-the-right-loadfamily-function-in-revitpythonshell):
18 |
19 | > revitpythonshell provides two very similar methods to load a family.
20 | >
21 | >```python
22 | >LoadFamily(self: Document, filename:str) -> (bool, Family)
23 | >LoadFamily(self: Document, filename:str) -> bool
24 | >```
25 | >
26 | >So it seems like only the return values are different. I have tried to calling it in several different ways:
27 | >
28 | >(success, newFamily) = doc.LoadFamily(path)
29 | >success, newFamily = doc.LoadFamily(path)
30 | >o = doc.LoadFamily(path)
31 | >But I always just get a bool back. I want the Family too.
32 |
33 | What is happening here is that the c# definitions of the method are:
34 |
35 | ```c#
36 | public bool LoadFamily(
37 | string filename
38 | )
39 | ```
40 | and
41 | ```c#
42 | public bool LoadFamily(
43 | string filename,
44 | out Family family
45 | )
46 | ```
47 |
48 | The IronPython syntax candy for `out` parameters, returning a tuple of results, can't automatically be selected here, because calling `LoadFamily` with just a string argument matches the first method overload.
49 |
50 | You can get at the overload you are looking for like this:
51 |
52 | ```python
53 | import clr
54 | family = clr.Reference[Family]()
55 | # family is now an Object reference (not set to an instance of an object!)
56 | success = doc.LoadFamily(path, family) # explicitly choose the overload
57 | # family is now a Revit Family object and can be used as you wish
58 | ```
59 | This works by creating an object reference to pass into the function and the method overload resultion thingy now knows which one to look for.
60 |
61 | Working under the assumption that the list of overloads shown in the RPS help is the same order as they appear, you can also do this:
62 |
63 | ```python
64 | success, family = doc.LoadFamily.Overloads.Functions[0](path)
65 | ```
66 |
67 | and that will, indeed, return a tuple `(bool, Autodesk.Revit.DB.Family)`. I just don't think you should be doing it that way, as it introduces a dependency on the order of the method overloads - I wouldn't want that smell in my code...
68 |
69 | Note, that this has to happen inside a transaction, so a complete example might be:
70 |
71 | ```python
72 | import clr
73 | t = Transaction(doc, 'loadfamily')
74 | t.Start()
75 | try:
76 | family = clr.Reference[Family]()
77 | success = doc.LoadFamily(path, family)
78 | # do stuff with the family
79 | t.Commit()
80 | except:
81 | t.Rollback()
82 | ```
83 |
--------------------------------------------------------------------------------
/book.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/collecting_elements/README.md:
--------------------------------------------------------------------------------
1 | # Collecting Elements
2 |
3 | Here is some examples on collecting elements using the `FilteredElementCollector`.
4 | More information about the class can be found [here](http://www.revitapidocs.com/2016/263cf06b-98be-6f91-c4da-fb47d01688f3.htm)
5 |
6 | ```python
7 | # Collect all Walls
8 | walls = FilteredElementCollector(doc).OfClass(Wall)
9 |
10 | # Collecting all Instances from category Furniture
11 | collector = FilteredElementcollector(doc)
12 | furniture_instances = collector.OfCategory(BuiltInCategory.OST_Furniture).WhereElementIsNotElementType()
13 | ```
14 |
--------------------------------------------------------------------------------
/connecting_to_a_sqlite_database/README.md:
--------------------------------------------------------------------------------
1 | # Connecting to a SQLite database
2 |
--------------------------------------------------------------------------------
/creating_geometry/README.md:
--------------------------------------------------------------------------------
1 | # Creating Geometry
2 |
--------------------------------------------------------------------------------
/deploying_rpsaddins/README.md:
--------------------------------------------------------------------------------
1 | # Deploying RpsAddins
2 |
3 |
--------------------------------------------------------------------------------
/deploying_rpsaddins/creating_the_installer.md:
--------------------------------------------------------------------------------
1 | # Creating the Installer
2 |
3 | Your installer will need to create a Revit addin manifest file (FIXME: check what Revit actually calls these files) and place it in the appropriate directory. See the Revit documentation on how to actually do this. Below is an example template of such an addin manifest:
4 |
5 | ```xml
6 |
7 |
8 |
9 | YOUR_ADDIN_NAME
10 | C:\PATH\TO\YOUR\ADDIN\ASSEMBLY
11 | GENERATE_AND_INSERT_GUID_HERE
12 | YOUR_ADDIN_NAME
13 | REGISTER_YOUR_VENDOR_ID
14 |
15 |
16 | ````
17 | You can call the AddIn whatever you like. The `FullClassName` should be the same as the basename of the RpsAddin manifest file - that is what the class generated by DeployRpsAddin is called.
--------------------------------------------------------------------------------
/deploying_rpsaddins/creating_the_rpsaddin_manifest.md:
--------------------------------------------------------------------------------
1 | # Creating the RpsAddin manifest
2 |
3 | A sample RpsAddin manifest looks like this:
4 |
5 | ```xml
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ```
14 |
15 | This example is taken from the *Examples* folder in the RevitPythonShell code. The file is called `HelloWorld.xml`. The name of the file is important, since the "Deploy RpsAddin" feature uses that file to name the resulting RpsAddin dll - in this case it will be called `HelloWorld.dll`.
16 |
17 | Let's look at the various parts of the manifest:
18 |
19 | * ``: this is the root tag describing an RpsAddin manifest file
20 | * ``: you can create as many panels on the ribbon as you like - they will be placed on the "Add-Ins" ribbon in Revit.
21 | * `text`: the `text` attribute contains the text of the resulting ribbon panel
22 | * ``: each ribbon panel may have one or more push buttons. Each such push button has a `text` attribute that is displayed in the interface and a `src` attribute that is the path (either absolute or relative to the RpsAddin manifest file) to the external script being configured.
23 |
24 | If you would like to add a `SplitButton`, you can do so like this:
25 |
26 | ```xml
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ```
43 |
44 | You can also add a *StartupScript* that will be run when the RpsAddin is loaded by Revit at startup:
45 |
46 | ```xml
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | ```
56 |
57 | You could even create an RpsAddin manifest file without any push buttons at all and just use the *StartupScript* to create the user interface on the Revit ribbon. See "Using the StartupScript to modify the RibbonPanel" on how to do this. Note that if you do this, the scripts won't be automatically added to the resulting RpsAddin dll. You can use the `File` tag to add files to the output directory. Use the installer to then bundle them up for installation:
58 |
59 | ```xml
60 |
61 |
62 |
63 |
64 |
65 | ```
66 | In the example above, the file `helloworld.py` is copied to the output directory. Your startup script (`good_morning_world.py`) then needs to reference this file.
--------------------------------------------------------------------------------
/deploying_rpsaddins/what_happens_when_deploying_an_rpsaddin.md:
--------------------------------------------------------------------------------
1 | # What happens when deploying an RpsAddin
2 |
3 | FIXME: add a figure here showing the files and how they relate.
4 |
5 | This is what happens behind the scenes when deploying an RpsAddin:
6 |
7 | * RevitPythonShell reads in the RpsAddin manifest file (the file you select in the Open File Dialog)
8 | * RevitPythonShell creates a DLL file in an output directory (FIXME: check output directory name) with a class that implements IExternalApplication and also subclasses RevitPythonShell.Rps(FIXME: look this up too)
9 | * RevitPythonShell adds all scripts mentioned in the RpsAddin manifest file as resources to this DLL
10 | * RevitPythonShell copies the relevant runtime files (RpsRuntime.dll, but also the IronPython runtime dlls) to the output directory
11 |
12 | If you want to deploy the resulting addin, you still need to create a Revit addin manifest file (not to be confused with the RpsAddin manifest file) and place that in the appropriate location (e.g. `%APPDATA%\Autodesk\Revit\Addins\2015`). You will also need to add any other files / python modules your scripts load. See "Creating the Installer" for more information.
--------------------------------------------------------------------------------
/deploying_rpsaddins/what_is_a_rpsaddin.md:
--------------------------------------------------------------------------------
1 | # What is a RpsAddin
2 |
3 | An RpsAddin refers to a Revit Addin that was written using the RevitPythonShell and then bundled for deployment using the "Deploy RpsAddin" feature of the RevitPythonShell.
4 |
5 | RpsAddins allow you to easily deploy your scripts without having to install and configure RevitPythonShell on each machine. It does this by creating a Revit DLL that can be used as an Addin. This DLL will use some runtime glue code located in `RpsRuntime.dll` to provide IronPython integration for the Addin. The DLL also contains your scripts and a manifest for the addin as string resources.
--------------------------------------------------------------------------------
/examples__cookbook.md:
--------------------------------------------------------------------------------
1 | # Examples / Cookbook
2 |
3 |
--------------------------------------------------------------------------------
/external_scripts.md:
--------------------------------------------------------------------------------
1 | # External Scripts
2 |
3 | External scripts are the RPS equivalent of `ExternalCommand`s in the Revit API - except you can edit them and re-run them without restarting Revit!
4 |
5 | As you work with the interactive shell (or better, the IronPython pad below the shell), you might create a set of statements that are reusable. If you save these to a file, you can add them to the Revit Ribbon panel in the Add-ins section. They will show up as buttons that you can press to have the script executed. By opening the script with your favorite editor, you can change what happens when the button get's pressed.
6 |
7 | 
8 |
9 | Use the "Configure" dialog to configure the external scripts. On the "External Scripts" tab, you can add new external scripts, remove them and edit them. Each entry in the list of external scripts has these properties:
10 |
11 | - a "Name": this is the text that is shown next to the button in the interface
12 | - a "Group": use a shared group name to add multiple external scripts to a "SplitButton" (see the "Button fixe" example below) or leave this blank to create stacked buttons (like "Button one" etc. in the example below)
13 | - a "Path": the full path to the python script to be run when the button is clicked.
14 |
15 | When you click "Save", you will be reminded to restart Revit in order to show changes to the Ribbon. The reason being, that Revit Add-ins can only manipulate the Ribbon when Revit starts. So. Close Revit and re-open it to see the following panel in your Add-ins section of the Revit Ribbon:
16 |
17 | 
18 |
19 | If you want to create more fancy controls, you should read the section "More control over the Ribbon Panel".
--------------------------------------------------------------------------------
/frequently_asked_questions/README.md:
--------------------------------------------------------------------------------
1 | # Frequently Asked Questions
2 |
3 |
--------------------------------------------------------------------------------
/hello_world/README.md:
--------------------------------------------------------------------------------
1 | # Hello World!
2 |
--------------------------------------------------------------------------------
/how-to-write-in-the-url-field-of-a-family-with-the-revitpythonshell.md:
--------------------------------------------------------------------------------
1 | Here is some code showing how to write the URL parameter of a `WallType` with the [RevitPythonShell](https://github.com/architecture-building-systems/revitpythonshell). This was created as an answer to the [RevitPythonShell group forum](https://groups.google.com/forum/#!forum/revitpythonshell). This method should work for other families as well!
2 |
3 | ```python
4 | # find a WallType to work with...
5 | collector = FilteredElementCollector(doc).OfClass(WallType)
6 | wt = [wt for wt in collector
7 | if Element.Name.__get__(wt).startswith('_dpv AW1')][0]
8 | print Element.Name.__get__(wt)
9 | parameter = wt.get_Parameter(BuiltInParameter.ALL_MODEL_URL)
10 | transaction = Transaction(doc, 'setting URL')
11 | try:
12 | transaction.Start()
13 | parameter.Set('http://systems.arch.ethz.ch')
14 | transaction.Commit()
15 | except:
16 | transaction.Rollback()
17 | ```
18 |
19 | The tricky part is the line with `wt.get_Parameter(BuiltInParameter.ALL_MODEL_URL)`. I found the `ALL_MODEL_URL` parameter name using the [RevitLookup tool](https://github.com/jeremytammik/RevitLookup) - which can be launched directly from RPS if you have it installed (just type `lookup(wt)`). The basic steps are:
20 |
21 | 1. retrieve the parameter (`Element.get_Parameter` - you can use the `BuiltInParameter` enumeration for standard parameters...)
22 | 2. set the value of the parameter (`Parameter.Set`)
23 | 3. since this changes the BIM model, you need to wrap it all in a `Transaction`.
--------------------------------------------------------------------------------
/installing_revitpythonshell_for_autodesk_revit/README.md:
--------------------------------------------------------------------------------
1 | # Installing RevitPythonShell for Autodesk Revit
2 |
--------------------------------------------------------------------------------
/installing_revitpythonshell_for_autodesk_revit/files_and_locations.md:
--------------------------------------------------------------------------------
1 | # Files and Locations
2 |
3 | FIXME: move this to an appendix as most people won't care about this!
4 |
5 | Create a list of all the files installed with RevitPythonShell. Also, a list of files (generally) created by RPS. Add a glossary for terms used here. Find a canonical way to refer to "canned commands" (external scripts, analog to `ExternalCommand`)
6 |
7 | The RevitPythonShell installer creates the following folders:
8 |
9 | * the *application folder*
10 | * defaults to `C:\Program Files (x86)\RevitPythonShell2015` on most systems
11 | * note: the `2015` portion means that this is the version for Autodesk Revit 2015 - other versions will differ accordingly
12 | * the *data folder*
13 | * `%APPDATA%\RevitPythonShell2015`, with `%APPDATA%` being a system variable that normally points to the `AppData\Roaming` subfolder of your user directory.
14 | * note: you can enter `%APPDATA%\RevitPythonShell2015` in the Windows Explorer and you will be taken directly to the correct folder. You can even `cd` there on the command line!
15 |
16 | Also, it adds an addin manifest file to `%APPDATA%\Autodesk\Revit\2015` called `RevitPythonShell2015` that tells Revit to load the plugin on startup. For more information on this, see the official tutorial on writing plug-ins for Autodesk Revit here: [My First Plug-in Training](http://usa.autodesk.com/adsk/servlet/index?siteID=123112&id=16849339) - see the section "Writing an AddIn Manifest" where this is explicitly discussed.
17 |
18 | ## Contents of the Application Folder
19 |
20 | The application folder contains the code required to run RevitPythonShell as a
21 |
22 | ## Contents of the Data Folder
23 |
24 |
--------------------------------------------------------------------------------
/ironpython_notes.md:
--------------------------------------------------------------------------------
1 | # IronPython notes
2 |
3 |
--------------------------------------------------------------------------------
/limitations_of_the_non-modal_shell/README.md:
--------------------------------------------------------------------------------
1 | # Limitations of the non-modal Shell
2 |
3 |
--------------------------------------------------------------------------------
/more_control_over_the_ribbonpanel.md:
--------------------------------------------------------------------------------
1 | # More control over the RibbonPanel
2 |
3 | This post explains how to exert more control over the items shown in the RibbonPanel by your RevitPythonShell scripts.
4 |
5 | The default behaviour of RevitPythonShell for external scripts is to group them into split buttons and place the remaining scripts in stacks of up to three. External scripts that are assigned the same “Group” value are placed together in a split button - in the example below, the external scripts “Button five”, “Button six” and “Button seven” are all assigned the group value “Group 5-7”.
6 |
7 | 
8 |
9 | After saving your changes, you need to restart Revit to see any changes. The Ribbon will then include this panel:
10 |
11 | 
12 |
13 | This is fine for collecting your personal scripts and while developing new scripts. When it comes to publishing a plugin (even inside your organization) you might want more control of how the buttons appear.
14 |
15 | **NOTE**: When you create a [RpsAddin for deployment](http://darenatwork.blogspot.ch/2013/05/deploying-rps-scripts-with.html), you use a slightly different approach by specifying the buttons in the RpsAddin xml file. But you are still limited to PushButtons.
16 |
17 | The Revit API contains a selection of controls you can use to execute scripts that you can use instead. You can find out all about these in the [Revit API Developers Guide (Ribbon Panels and Controls)](http://help.autodesk.com/view/RVT/2015/ENU/?guid=GUID-1547E521-59BD-4819-A989-F5A238B9F2B3).
18 |
19 | The Revit API expects you to build your Ribbon panels and controls while the application is starting up - during the `IExternalApplication.OnStartup` method of a plugin. After that, the access to the `UIControlledApplication` which is needed to alter the Ribbon is not available anymore. Therefore, you can only access the `UIControlledApplication` object in the RPS [startup script](http://darenatwork.blogspot.ch/2013/05/new-feature-startupscript-in.html) - through a special variable called `__uiControlledApplication__`. The startup script in RPS gives you access to the very variable it uses to configure its own user interface. All we need to do now is hook up an RPS script with a button on the ribbon. Let us assume a very simple script:
20 |
21 | ```python
22 | """
23 | helloworld.py - prints a greeting
24 | """
25 | print 'hello, world!'
26 | ```
27 | (you can find this script in the [rps-sample-scripts project](https://github.com/daren-thomas/rps-sample-scripts/blob/master/helloworld.py) on GitHub)
28 |
29 | This script shall be called whenever the user clicks a button or other control on our custom Ribbon panel.
30 |
31 | There is just one problem here. When you create a control on a Revit Ribbon panel, you need to pass in the path to a DLL and the fully qualified name (including namespaces) of a class inside that DLL that implements `IExternalCommand`. That does not sound like a python script at all!
32 |
33 | To support this, the RevitPythonShell exposes a special class called `ExternalCommandAssemblyBuilder` which can create such a DLL. To use it, you just pass in a dictionary of class names and the corresponding python script files. The DLL created is just a very thin wrapper that calls into the `RpsRuntime.dll` (yes, that means you can use this for your RpsAddins!) with the script path and gets them executed.
34 |
35 | So, a simple startup script that creates a Ribbon panel with a single `PushButton` would look like this:
36 |
37 | ```python
38 | '''
39 | simple_ribbon.py - creates a ribbon panel with a single push button.
40 |
41 | NOTE:
42 | - this MUST be set as a startup script for it to work
43 | - the RPS variable "EXAMPLES_PATH" must be set and contain "helloworld.py"
44 | '''
45 |
46 | # script that is run when Revit starts in the IExternalApplication.Startup event.
47 | try:
48 | import os
49 | from RevitPythonShell.RpsRuntime import ExternalCommandAssemblyBuilder
50 | from Autodesk.Revit.UI import *
51 |
52 | SCRIPT_PATH = os.path.join(__vars__['EXAMPLES_PATH'], "helloworld.py")
53 | DLL_PATH = os.path.expandvars(r"%APPDATA%\RevitPythonShell2015\simple_ribbon.dll")
54 | print 'storing external command assembly here:', DLL_PATH
55 |
56 | builder = ExternalCommandAssemblyBuilder()
57 | builder.BuildExternalCommandAssembly(
58 | DLL_PATH,
59 | {'HelloWorld': SCRIPT_PATH})
60 |
61 | panel = __uiControlledApplication__.CreateRibbonPanel('simple_ribbon')
62 |
63 | pbd = PushButtonData('pb_HelloWorld', 'hello, world!', DLL_PATH, 'HelloWorld')
64 | panel.AddItem(pbd)
65 |
66 | #__window__.Close() # closes the window
67 | except:
68 | import traceback # note: add a python27 library to your search path first!
69 | traceback.print_exc() # helps you debug when things go wrong
70 | ```
71 | (NOTE: you can find this script in the [rps-sample-scripts repository](https://github.com/daren-thomas/rps-sample-scripts/blob/master/StartupScripts/simple_ribbon.py) on GitHub)
72 |
73 | The script (and all following scripts in this post) assumes you have the directory structure of the rps-sample-scripts repository downloaded to your machine - a simple git clone should do the trick - and have set the RPS variable `EXAMPLES_PATH` to point to that folder. You could also just edit the script text to hard code the paths and live with the nasty code smell...
74 |
75 | Adding `simple_ribbon.py` as your startup script will result in a new ribbon panel added to RPS:
76 |
77 | 
78 |
79 | When clicked, you will be greeted as expected.
80 |
81 | There are a few things going on here, so let’s go through them one by one. First, the whole body of the script is wrapped in a try/except. This is important, since a crash in the script might bring down the whole of Revit and you will not know where it happened - so we use `traceback.print_exc()` to print an exception trace. This is a handy debugging tip for your RPS scripting skill set!
82 |
83 | Next, we create an assembly for Revit to load when you click the push button. The arguments to `builder.BuildExternalCommandAssembly` include the path of the assembly to be created. I chose to place it in the same folder as the `CommandLoaderAssembly.dll` - which is a similar dll created by RPS for the external scripts defined in the Configure dialog. You can place it anywhere the script has write access to.
84 |
85 | Adding a panel to the ribbon and adding a push button to the panel is basically just an exercise of translating the sample c# code from the SDK into python. Adding images is also possible. Consider the following revised version:
86 |
87 | % simple_ribbon_with_icons.py (includes images)
88 |
89 | This produces a Ribbon panel that looks like this:
90 |
91 | 
92 |
93 | As far as I can tell, you only really need to set the `LargeImage` property of the `PushButtonData` object - the `Image` property seems to be an atavism from an ancient version of Revit...
94 |
95 | The [rps-sample-scripts repository on GitHub](https://github.com/daren-thomas/rps-sample-scripts/blob/master/StartupScripts/new_ribbon_panel.py) contains a translation of the [New Ribbon Panel example from the Revit API Developer’s Guide](http://help.autodesk.com/view/RVT/2015/ENU/?guid=GUID-1547E521-59BD-4819-A989-F5A238B9F2B3):
96 |
97 | ```python
98 | """
99 | new_ribbon_panel.py - a startup script to create a selection of
100 | controls on the ribbon.
101 |
102 | This script is based on the New Ribbon Panel and Controls example in the
103 | Revit API Devolopers Guide.
104 |
105 | NOTE:
106 | - this MUST be set as a startup script for it to work
107 | - the RPS variable "EXAMPLES_PATH" must be set and contain "helloworld.py"
108 | """
109 |
110 | import os
111 | import clr
112 | clr.AddReference('PresentationCore')
113 | from System.Windows.Media.Imaging import BitmapImage
114 | from System import Uri
115 | from RevitPythonShell.RpsRuntime import ExternalCommandAssemblyBuilder
116 | from Autodesk.Revit.UI import *
117 |
118 | SCRIPT_PATH = os.path.join(__vars__['EXAMPLES_PATH'], "helloworld.py")
119 | LARGE_IMG_PATH = os.path.join(__vars__['EXAMPLES_PATH'], "PythonScript32x32.png")
120 | SMALL_IMG_PATH = os.path.join(__vars__['EXAMPLES_PATH'], "PythonScript16x16.png")
121 | EXAMPLES_PATH = __vars__['EXAMPLES_PATH']
122 | DLL_PATH = os.path.expandvars(r"%APPDATA%\RevitPythonShell2015\simple_ribbon.dll")
123 | print 'storing external command assembly here:', DLL_PATH
124 |
125 |
126 | def create_ribbon_panel():
127 | panel = __uiControlledApplication__.CreateRibbonPanel("New Ribbon Panel")
128 | add_radio_group(panel)
129 | panel.AddSeparator()
130 | add_push_button(panel)
131 | panel.AddSeparator()
132 | add_split_button(panel)
133 | panel.AddSeparator()
134 | add_stacked_buttons(panel)
135 | panel.AddSeparator()
136 | add_slide_out(panel)
137 |
138 |
139 | def add_radio_group(panel):
140 | """add radio button group"""
141 | radio_data = RadioButtonGroupData("radioGroup")
142 | radio_button_group = panel.AddItem(radio_data)
143 |
144 | tb1 = ToggleButtonData("toggleButton1", "Red")
145 | tb1.ToolTip = "Red Option"
146 | tb1.LargeImage = BitmapImage(Uri(os.path.join(
147 | EXAMPLES_PATH, 'StartupScripts', 'red.png')))
148 |
149 | tb2 = ToggleButtonData("toggleButton2", "Green")
150 | tb2.ToolTip = "Green Option"
151 | tb2.LargeImage = BitmapImage(Uri(os.path.join(
152 | EXAMPLES_PATH, 'StartupScripts', 'green.png')))
153 |
154 | tb3 = ToggleButtonData("toggleButton3", "Blue")
155 | tb3.ToolTip = "Blue Option"
156 | tb3.LargeImage = BitmapImage(Uri(os.path.join(
157 | EXAMPLES_PATH, 'StartupScripts', 'blue.png')))
158 |
159 | radio_button_group.AddItem(tb1)
160 | radio_button_group.AddItem(tb2)
161 | radio_button_group.AddItem(tb3)
162 |
163 |
164 | def add_push_button(panel):
165 | """add push button"""
166 | push_button = panel.AddItem(
167 | PushButtonData("pb_HelloWorld", "Hello, world!",
168 | DLL_PATH, "HelloWorld"))
169 | push_button.ToolTip = "Say hello world"
170 | context_help = ContextualHelp(ContextualHelpType.Url, "http://www.autodesk.com")
171 | push_button.SetContextualHelp(context_help)
172 |
173 | push_button.LargeImage = BitmapImage(Uri(LARGE_IMG_PATH))
174 |
175 |
176 | def add_split_button(panel):
177 | """add a split button"""
178 | button_one = PushButtonData("pbButtonOne", "Option one",
179 | DLL_PATH, "HelloWorld")
180 | button_one.LargeImage = BitmapImage(Uri(os.path.join(
181 | EXAMPLES_PATH, 'StartupScripts', 'one.png')))
182 |
183 | button_two = PushButtonData("pbButtonTwo", "Option two",
184 | DLL_PATH, "HelloWorld")
185 | button_two.LargeImage = BitmapImage(Uri(os.path.join(
186 | EXAMPLES_PATH, 'StartupScripts', 'two.png')))
187 |
188 | button_three = PushButtonData("pbButtonThree", "Option three",
189 | DLL_PATH, "HelloWorld")
190 | button_three.LargeImage = BitmapImage(Uri(os.path.join(
191 | EXAMPLES_PATH, 'StartupScripts', 'three.png')))
192 |
193 | split_button = panel.AddItem(SplitButtonData("splitButton", "Split"))
194 | split_button.AddPushButton(button_one)
195 | split_button.AddPushButton(button_two)
196 | split_button.AddPushButton(button_three)
197 |
198 |
199 | def add_stacked_buttons(panel):
200 | """Add a text box and combo box as stacked items"""
201 | combo_box_data = ComboBoxData("comboBox")
202 | text_data = TextBoxData("Text Box")
203 | text_data.Image = BitmapImage(Uri(SMALL_IMG_PATH))
204 | text_data.Name = "Text Box"
205 | text_data.ToolTip = "Enter some text here"
206 | text_data.LongDescription = """This is text that will appear next to the image
207 | when the user hovers the mouse over the control"""
208 | text_data.ToolTipImage = BitmapImage(Uri(LARGE_IMG_PATH))
209 |
210 | stacked_items = panel.AddStackedItems(text_data, combo_box_data)
211 |
212 | text_box = stacked_items[0]
213 | text_box.PromptText = "Enter a comment"
214 | text_box.ShowImageAsButton = True
215 | text_box.ToolTip = "Enter some text"
216 | text_box.EnterPressed += lambda sender, args: TaskDialog.Show('new_ribbon_panel', sender.Value)
217 |
218 | combo_box = stacked_items[1]
219 | combo_box.ItemText = "ComboBox"
220 | combo_box.ToolTip = "Select an Option"
221 | combo_box.LongDescription = "Select a number or letter"
222 |
223 | member_data_a = ComboBoxMemberData('A', 'Option A')
224 | member_data_a.Image = BitmapImage(Uri(os.path.join(
225 | EXAMPLES_PATH, 'StartupScripts', 'a.png')))
226 | member_data_a.GroupName = 'Letters'
227 | combo_box.AddItem(member_data_a)
228 |
229 | member_data_b = ComboBoxMemberData('B', 'Option B')
230 | member_data_b.Image = BitmapImage(Uri(os.path.join(
231 | EXAMPLES_PATH, 'StartupScripts', 'b.png')))
232 | member_data_b.GroupName = 'Letters'
233 | combo_box.AddItem(member_data_b)
234 |
235 | member_data_c = ComboBoxMemberData('C', 'Option C')
236 | member_data_c.Image = BitmapImage(Uri(os.path.join(
237 | EXAMPLES_PATH, 'StartupScripts', 'c.png')))
238 | member_data_c.GroupName = 'Letters'
239 | combo_box.AddItem(member_data_c)
240 |
241 | member_data_1 = ComboBoxMemberData('1', 'Option 1')
242 | member_data_1.Image = BitmapImage(Uri(os.path.join(
243 | EXAMPLES_PATH, 'StartupScripts', 'one_small.png')))
244 | member_data_1.GroupName = 'Numbers'
245 | combo_box.AddItem(member_data_1)
246 |
247 | member_data_2 = ComboBoxMemberData('2', 'Option 2')
248 | member_data_2.Image = BitmapImage(Uri(os.path.join(
249 | EXAMPLES_PATH, 'StartupScripts', 'two_small.png')))
250 | member_data_2.GroupName = 'Numbers'
251 | combo_box.AddItem(member_data_2)
252 |
253 | member_data_3 = ComboBoxMemberData('3', 'Option 3')
254 | member_data_3.Image = BitmapImage(Uri(os.path.join(
255 | EXAMPLES_PATH, 'StartupScripts', 'three_small.png')))
256 | member_data_3.GroupName = 'Numbers'
257 | combo_box.AddItem(member_data_3)
258 |
259 | def add_slide_out(panel):
260 | pass # left as exercise for the reader :)
261 |
262 |
263 | if __name__ == '__main__':
264 | try:
265 | builder = ExternalCommandAssemblyBuilder()
266 | builder.BuildExternalCommandAssembly(
267 | DLL_PATH,
268 | {'HelloWorld': SCRIPT_PATH})
269 | create_ribbon_panel()
270 | except:
271 | import traceback
272 | traceback.print_exc()
273 | ```
274 |
275 | If you are really interested on what is produced by the `ExternalCommandAssemblyBuilder`, just use [JetBrains dotPeek](https://www.jetbrains.com/decompiler/) to decompile the `simple_ribbon.dll`. When I tried, I ended up with this:
276 |
277 | 
278 |
279 | I'll leave it as an exercise to the reader to get on GitHub and [check out the source for `RpsExternalCommandScriptBase`](https://github.com/architecture-building-systems/revitpythonshell/blob/master/RpsRuntime/RpsExternalCommandBase.cs) - it has a constructor that saves the script path as a member to be called when Revit decides to `Execute` it.
--------------------------------------------------------------------------------
/predefined_variables/README.md:
--------------------------------------------------------------------------------
1 | # Predefined variables
2 |
3 | In addition to the builtin variables you would expect in python, the RevitPythonShell adds some special variables to enable access to Autodesk Revit.
4 |
5 | Some of these variables can only be understood in the context of implementing `IExternalCommand.Execute` in the Revit API, so check there if you are not quite sure what to make of a specific variable.
6 |
7 | | Variable | Description |
8 | | -- | -- |
9 | | `__revit__` | A reference to the `Autodesk.Revit.Application` instance, obtained from the `ExternalCommandData` aurgument passed to plugins. |
10 | | `__commandData__` | The actual `ExternalCommandData`argument passed to the RevitPythonShell plugin when you clicked "Open Python Shell" or launched a script from the ribbon. On closing the interactive shell window, the contents of `__message__` will be assigned back so Revit has access to it.|
11 | | `__elements__` | The `ElementSet` passed to the RevitPythonShell when you clicked "Open Python Shell" or launched a script from the ribbon. |
12 | | `__result__` | This is set to `IExternalCommand.Result.Succeeded`, but you can change it if you want. When the interactive shell is closed or a script ends, the RevitPythonShell returns the value of this variable as the result of the `IExternalCommand.Execute` method.|
13 | |`__vars__` | This is a `IDictionary` of user defined variables as defined in the configuration file|
14 | | `__uiControlledApplication__` | (only for the *StartupScript*) A reference to the `UIControlledApplication`instance.
15 | | `__window__` | A reference to the current output window. Basically, only `__window__.Close()` is guaranteed to work, but poke around!|
16 | | `__file__` | External scripts and those deployed with RpsAddin support the `__file__` builtin - the variable contains the full path to the source file being executed. For scripts that are read from a dll (ExternalCommandAssemblyBuilder / RpsAddin) the absolute path to the dll is used as the base directory of the script path (e.g. 'C:\myfolder\myaddin.dll\myscript.py') |
17 |
18 | A script that is invoked as a *StartupScript* (when Revit is starting up) has access to the `UIControlledApplication` instance just like regular Revit plugins. You can use this to customize the ribbon for your plugin - this is especially useful when developing self-contained RpsAddins for deployment. See "Using the StartupScript to modify the RibbonPanel" for more information. (FIXME)
19 |
20 | ## Other predefined variables
21 |
22 | Depending on the contents of your *InitScript*, when you open an interactive shell, you will find other variables predefined too. The default *InitScript* contains these values:
23 |
24 | - the contents of the namespaces `Autodesk.Revit.DB`, `Autodesk.Revit.DB.Architecture`, `Autodesk.Revit.DB.Analysis``
25 | - `uidoc`: an alias for `__revit__.ActiveUIDocument`
26 | - `doc`: an alias for `__revit__.ActiveUIDocument.Document
27 | - `selection`: an alias for `list(__revit__.ActiveUIDocument.Selection.Elements)
28 | - `TaskDialog` (the class)
29 | - `UIApplication` (the class)
30 | - `alert(msg)`: shortcut for `TaskDialog.Show(...)`
31 | - `quit()`: closes the window
32 |
33 | ## User defined variables (__vars__)
34 | (FIXME)
35 | - writeable dict
36 |
--------------------------------------------------------------------------------
/revitlookup_and_revitpythonshell.md:
--------------------------------------------------------------------------------
1 | # RevitLookup and RevitPythonShell
2 |
3 | [RevitLookup](https://github.com/jeremytammik/RevitLookup) is a handy tool for interactively exploring Revit BIM databases. If you want to get anywhere with Revit API programming, you should look into RevitLookup. You should also be reading the RevitLookup's author Jeremy Tammik's blog [The Building Coder](http://thebuildingcoder.typepad.com/)!
4 |
5 | The standard RevitPythonShell *InitScript* contains a section dedicated to RevitLookup:
6 |
7 | ```python
8 | #------------------------------------------------------------------------------
9 | import clr
10 | from Autodesk.Revit.DB import ElementSet, ElementId
11 |
12 | class RevitLookup(object):
13 | def __init__(self, uiApplication):
14 | '''
15 | for RevitSnoop to function properly, it needs to be instantiated
16 | with a reference to the Revit Application object.
17 | '''
18 | # find the RevitLookup plugin
19 | try:
20 | rlapp = [app for app in uiApplication.LoadedApplications
21 | if app.GetType().Namespace == 'RevitLookup'
22 | and app.GetType().Name == 'App'][0]
23 | except IndexError:
24 | self.RevitLookup = None
25 | return
26 | # tell IronPython about the assembly of the RevitLookup plugin
27 | clr.AddReference(rlapp.GetType().Assembly)
28 | import RevitLookup
29 | self.RevitLookup = RevitLookup
30 | # See note in CollectorExt.cs in the RevitLookup source:
31 | self.RevitLookup.Snoop.CollectorExts.CollectorExt.m_app = uiApplication
32 | self.revit = uiApplication
33 |
34 | def lookup(self, element):
35 | if not self.RevitLookup:
36 | print 'RevitLookup not installed. Visit https://github.com/jeremytammik/RevitLookup to install.'
37 | return
38 | if isinstance(element, int):
39 | element = self.revit.ActiveUIDocument.Document.GetElement(ElementId(element))
40 | if isinstance(element, ElementId):
41 | element = self.revit.ActiveUIDocument.Document.GetElement(element)
42 | if isinstance(element, list):
43 | elementSet = ElementSet()
44 | for e in element:
45 | elementSet.Insert(e)
46 | element = elementSet
47 | form = self.RevitLookup.Snoop.Forms.Objects(element)
48 | form.ShowDialog()
49 | _revitlookup = RevitLookup(__revit__)
50 | def lookup(element):
51 | _revitlookup.lookup(element)
52 |
53 | #------------------------------------------------------------------------------
54 | ```
55 | What this does is define a global function `lookup` that will open up the "Snoop Objects" dialog from RevitLookup with the element passed. In fact, you can pass `ElementId`s, lists of `Element` objects or just plain integers, that are converted to `ElementId`s - obtain these from the *Manage->IDs of Selection* command in the Ribbon. Since the `Document` also derives from `Element`, you can inspect it too!
56 |
57 | Here are some fun things to try, assuming you haven't removed anything from the standard *InitScript*:
58 |
59 | Select an element in the BIM, start an interactive Python shell and type:
60 |
61 | ```python
62 | >>> lookup(selection[0])
63 | ```
64 |
65 | You should see something like this:
66 |
67 | ![lookup(selection[0])](https://dl.dropboxusercontent.com/u/8112069/scripting-autodesk-revit-with-revitpythonshell/revitlookup_selection.png)
68 |
69 | If you don't have RevitLookup installed, you will instead get a message like this:
70 |
71 | ![lookup(selection[0]) - not installed](https://dl.dropboxusercontent.com/u/8112069/scripting-autodesk-revit-with-revitpythonshell/revitlookup_selection_notinstalled.png)
72 |
73 | Typing
74 |
75 | ```python
76 | >>> lookup(1157058) # make sure this is a valid ElementId!
77 | ```
78 | will result in something like this:
79 |
80 | ![lookup(selection[0]) - not installed](https://dl.dropboxusercontent.com/u/8112069/scripting-autodesk-revit-with-revitpythonshell/revitlookup_int.png)
--------------------------------------------------------------------------------
/the_configure_dialog/2015.02.27_Ribbon_Panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daren-thomas/scripting-autodesk-revit-with-revitpythonshell/520886f8bee7fbeec09777d1b78037d6a6453ab0/the_configure_dialog/2015.02.27_Ribbon_Panel.png
--------------------------------------------------------------------------------
/the_configure_dialog/2015.03.18_Configure_RevitPythonShell.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daren-thomas/scripting-autodesk-revit-with-revitpythonshell/520886f8bee7fbeec09777d1b78037d6a6453ab0/the_configure_dialog/2015.03.18_Configure_RevitPythonShell.png
--------------------------------------------------------------------------------
/the_configure_dialog/README.md:
--------------------------------------------------------------------------------
1 | # The "Configure" dialog
2 |
3 | The "Configure" dialog lets you configure RevitPythonShell. You can configure
4 |
5 | - external scripts
6 | - search paths
7 | - the InitScript and the StartupScript
8 | - and "environment" variables
--------------------------------------------------------------------------------
/the_configure_dialog/initScript-startupScript.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daren-thomas/scripting-autodesk-revit-with-revitpythonshell/520886f8bee7fbeec09777d1b78037d6a6453ab0/the_configure_dialog/initScript-startupScript.png
--------------------------------------------------------------------------------
/the_configure_dialog/initscript,_startupscript.md:
--------------------------------------------------------------------------------
1 | # InitScript, StartupScript
2 |
3 | The *InitScript / StartupScript* tab lets you choose the *InitScript* and *StartupScript*.
4 |
5 | 
6 |
7 | ## InitScript
8 |
9 | The *InitScript* is a script that is run whenever an interactive shell is opened. You can place imports, functions and variable definitions in the *InitScript* to make your life in interactive mode easier. Just keep in mind, that when you save code developed this way to a command (FIXME: external script?) you will need to add these to the external script.
10 |
11 | The default contents of the *InitScript* are:
12 |
13 | ```python
14 | # these commands get executed in the current scope
15 | # of each new shell (but not for canned commands)
16 | from Autodesk.Revit.DB import *
17 | from Autodesk.Revit.DB.Architecture import *
18 | from Autodesk.Revit.DB.Analysis import *
19 |
20 | uidoc = __revit__.ActiveUIDocument
21 | doc = __revit__.ActiveUIDocument.Document
22 |
23 | from Autodesk.Revit.UI import TaskDialog
24 | from Autodesk.Revit.UI import UIApplication
25 |
26 |
27 | def alert(msg):
28 | TaskDialog.Show('RevitPythonShell', msg)
29 |
30 |
31 | def quit():
32 | __window__.Close()
33 | exit = quit
34 |
35 |
36 | def get_selected_elements(doc):
37 | """API change in Revit 2016 makes old method throw an error"""
38 | try:
39 | # Revit 2016
40 | return [doc.GetElement(id)
41 | for id in __revit__.ActiveUIDocument.Selection.GetElementIds()]
42 | except:
43 | # old method
44 | return list(__revit__.ActiveUIDocument.Selection.Elements)
45 | selection = get_selected_elements(doc)
46 |
47 |
48 | #------------------------------------------------------------------------------
49 | import clr
50 | from Autodesk.Revit.DB import ElementSet, ElementId
51 |
52 | class RevitLookup(object):
53 | def __init__(self, uiApplication):
54 | '''
55 | for RevitSnoop to function properly, it needs to be instantiated
56 | with a reverence to the Revit Application object.
57 | '''
58 | # find the RevitLookup plugin
59 | try:
60 | rlapp = [app for app in uiApplication.LoadedApplications
61 | if app.GetType().Namespace == 'RevitLookup'
62 | and app.GetType().Name == 'App'][0]
63 | except IndexError:
64 | self.RevitLookup = None
65 | return
66 | # tell IronPython about the assembly of the RevitLookup plugin
67 | clr.AddReference(rlapp.GetType().Assembly)
68 | import RevitLookup
69 | self.RevitLookup = RevitLookup
70 | # See note in CollectorExt.cs in the RevitLookup source:
71 | self.RevitLookup.Snoop.CollectorExts.CollectorExt.m_app = uiApplication
72 | self.revit = uiApplication
73 |
74 | def lookup(self, element):
75 | if not self.RevitLookup:
76 | print 'RevitLookup not installed. Visit https://github.com/jeremytammik/RevitLookup to install.'
77 | return
78 | if isinstance(element, int):
79 | element = self.revit.ActiveUIDocument.Document.GetElement(ElementId(element))
80 | if isinstance(element, ElementId):
81 | element = self.revit.ActiveUIDocument.Document.GetElement(element)
82 | if isinstance(element, list):
83 | elementSet = ElementSet()
84 | for e in element:
85 | elementSet.Insert(e)
86 | element = elementSet
87 | form = self.RevitLookup.Snoop.Forms.Objects(element)
88 | form.ShowDialog()
89 | _revitlookup = RevitLookup(__revit__)
90 | def lookup(element):
91 | _revitlookup.lookup(element)
92 |
93 | #------------------------------------------------------------------------------
94 |
95 | # a fix for the __window__.Close() bug introduced with the non-modal console
96 | class WindowWrapper(object):
97 | def __init__(self, win):
98 | self.win = win
99 |
100 | def Close(self):
101 | self.win.Dispatcher.Invoke(lambda *_: self.win.Close())
102 |
103 | def __getattr__(self, name):
104 | return getattr(self.win, name)
105 | __window__ = WindowWrapper(__window__)
106 | ```
107 | The contents of this file sometimes changes from version to version of RevitPythonShell. You can always find the [newest version in the repository](https://raw.githubusercontent.com/architecture-building-systems/revitpythonshell/master/RevitPythonShell/init.py).
108 |
109 | ## StartupScript
110 |
111 | The *StartupScript* is run when RevitPythonShell is first initialized as part of the `IExternalApplication.OnStartup` event, just after the RevitPythonShell ribbon has been populated. In addition to the standard RevitPythonShell variables available to external scripts, the *StartupScript* has access to `__uiControlledApplication__` the instance of `UIControlledApplication` passed to Revit `IExternalApplication` implementations.
112 |
113 | Possible, real-world uses for this include:
114 |
115 | * creating custom ribbon panels (e.g. for deploying your addin)
116 | * starting a web server (see chapter "Webserver example")
117 | * unattended / "batch" processing of revit files
118 |
119 | (FIXME: allow *multiple* StartupScripts and move to separate tab)
120 |
121 | The initial *StartupScript* installed with RevitPythonShell looks like this:
122 |
123 | ```python
124 | # script that is run when RevitPythonShell starts in the IExternalApplication.Startup event.
125 | try:
126 | # add your code here
127 | # ...
128 | __window__.Close() # closes the window
129 | except:
130 | import traceback # note: add a python27 library to your search path first!
131 | traceback.print_exc() # helps you debug when things go wrong
132 | ```
133 |
134 | The contents of this file sometimes changes from version to version of RevitPythonShell. You can always find the newest version in the repository:
135 | https://code.google.com/p/revitpythonshell/source/browse/trunk/RevitPythonShell/startup.py
136 |
--------------------------------------------------------------------------------
/the_configure_dialog/search-dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daren-thomas/scripting-autodesk-revit-with-revitpythonshell/520886f8bee7fbeec09777d1b78037d6a6453ab0/the_configure_dialog/search-dialog.png
--------------------------------------------------------------------------------
/the_configure_dialog/search_paths.md:
--------------------------------------------------------------------------------
1 | # Search Paths
2 |
3 | You can use the *Search Paths* tab of the *Configure Commands* dialog to specify additional locations that will be searched when importing modules.
4 |
5 | 
6 |
7 | The *Add* button opens a dialog that lets you choose a folder to add. You can edit the selected folder either with the text box or by clicking *Browse...* and choosing a new folder. Use the *Remove* button to remove an entry in the search paths list.
8 |
9 | The search paths will be written to the configuration file (FIXME: link to chapter) when the *Configure Commands* dialog is closed with *Save* - clicking *Cancel* will undo any changes you made.
10 |
11 | The paths configured in the *Search Paths* tab are added to `sys.path` whenever an interactive shell is opened or an external script is executed.
12 |
13 | **NOTE**: The directory an external script resides in is automatically added to the search paths when that external script is run from the Ribbon. This makes it easier to split your code into a "library" portion and a "gui" portion and thus have re-usable code in your scripts.
14 |
15 | * FIXME: rename the dialog to *Configure* or *Preferences*
16 | * FIXME: rename the *Commands* tab to *external scripts* (or come up with a better name...)
--------------------------------------------------------------------------------
/the_configure_dialog/variables-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daren-thomas/scripting-autodesk-revit-with-revitpythonshell/520886f8bee7fbeec09777d1b78037d6a6453ab0/the_configure_dialog/variables-0.png
--------------------------------------------------------------------------------
/the_configure_dialog/variables-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daren-thomas/scripting-autodesk-revit-with-revitpythonshell/520886f8bee7fbeec09777d1b78037d6a6453ab0/the_configure_dialog/variables-1.png
--------------------------------------------------------------------------------
/the_configure_dialog/variables.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/the_configure_dialog/variables.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daren-thomas/scripting-autodesk-revit-with-revitpythonshell/520886f8bee7fbeec09777d1b78037d6a6453ab0/the_configure_dialog/variables.png
--------------------------------------------------------------------------------
/using_linq_in_ironpython/README.md:
--------------------------------------------------------------------------------
1 | # Using LINQ in IronPython
2 |
3 | You can use LINQ in IronPython by importing the extension methods from a namespace. This same technique can be used to use other extension methods too!
4 |
5 | ```python
6 | import clr
7 | clr.AddReference("System.Core")
8 | import System
9 | clr.ImportExtensions(System.Linq)
10 |
11 | # will print 3 and 4 :)
12 | [2, 3, 4].Where(lambda x: x != 2).ToList().ForEach(System.Console.WriteLine)
13 | ```
14 |
15 | This is not quite the same as the syntactic sugar you might be used to from c#. It could still come in handy if you want to use some of the more advanced LINQ methods.
16 |
17 | BTW: You can also use python list comprehensions for most use cases of LINQ!
18 |
19 |
20 |
--------------------------------------------------------------------------------
/using_the_startupscript_to_modify_the_ribbonpanel/README.md:
--------------------------------------------------------------------------------
1 | # Using the StartupScript to modify the RibbonPanel
2 |
3 |
--------------------------------------------------------------------------------
/webserver_example/README.md:
--------------------------------------------------------------------------------
1 | # Webserver example
2 |
3 | This is a more elaborate example that shows how to embedd a webserver in Autodesk Revit and use it to automate tasks.
4 |
5 | How do you access the BIM from outside Revit? With the Revit API it is easy to access the outside world from *within* Revit. Sometimes you want to write software that needs to read a schedule from a `.rvt` document - from *outside* of Revit.
6 |
7 | As an example, say you have a shell script that reads in schedule data from a Revit document and saves it to a CSV file.
8 |
9 | One way to solve this is to have Revit act as a web server, say, http://localhost:8080. You could then use curl:
10 |
11 | ```
12 | curl http://localhost:8080/schedules/my_schedule_name > my_local_file_name.csv
13 | ```
14 |
15 | Let us build a RevitPythonShell script that allows you to do just that: Export any schedule in the BIM as a CSV file through a web service. Depending on the URL requested, you could return a screenshot of the current view or ways to open / close documents:
16 |
17 | ```
18 | curl http://localhost:8080/screenshot
19 | curl http://localhost:8080/open/Desktop/Project1.rvt
20 | ```
21 |
22 | This is a variation on the [non-modal dialog issue](http://thebuildingcoder.typepad.com/blog/2010/04/asynchronous-api-calls-and-idling.html) ([see here too!](http://thebuildingcoder.typepad.com/blog/2013/12/replacing-an-idling-event-handler-by-an-external-event.html)). We want to run a web server in a separate thread, but have handling requests run in the main Revit thread so that we have access to the API. We will be using an [external event](http://help.autodesk.com/view/RVT/2016/ENU/?guid=GUID-0A0D656E-5C44-49E8-A891-6C29F88E35C0) to solve this.
23 |
24 | The web server itself uses the [`HttpListener`](https://msdn.microsoft.com/en-us/library/system.net.httplistener.aspx)class, which runs in a separate thread and just waits for new connections. These are then handled by pushing them into a queue and notifying the `ExternalEvent` that a new event has happened.
25 |
26 | This is where the script starts:
27 |
28 | ```python
29 | def main():
30 | contexts = ContextQueue()
31 | eventHandler = RpsEventHandler(contexts)
32 | externalEvent = ExternalEvent.Create(eventHandler)
33 | server = RpsServer(externalEvent, contexts)
34 | serverThread = Thread(ThreadStart(server.serve_forever))
35 | serverThread.Start()
36 | ```
37 |
38 | Whoa! What is going on here?
39 |
40 | - a communication channel `contexts` is created for sending web requests (stashed as `HttpListenerContext` instances) to the `ExternalEvent` thread.
41 | - an `IExternalEventHandler` implementation called `RpsEventHandler` that handles producing the output.
42 | - a web server wrapped in a method `serve_forever` that listens for web requests with the `HttpListener`, stores them into the context queue and notifies the external event that there is work to be done.
43 |
44 | We'll look into each component one by one below. Note: The full code can be found here in the [rps-sample-scripts GitHub repository](https://github.com/daren-thomas/rps-sample-scripts/blob/master/StartupScripts/RpsHttpServer/rpshttpserver.py).
45 |
46 | Let's start with the `ContextQueue`:
47 |
48 | ```python
49 | class ContextQueue(object):
50 | def __init__(self):
51 | from System.Collections.Concurrent import ConcurrentQueue
52 | self.contexts = ConcurrentQueue[HttpListenerContext]()
53 |
54 | def __len__(self):
55 | return len(self.contexts)
56 |
57 | def append(self, c):
58 | self.contexts.Enqueue(c)
59 |
60 | def pop(self):
61 | success, context = self.contexts.TryDequeue()
62 | if success:
63 | return context
64 | else:
65 | raise Exception("can't pop an empty ContextQueue!")
66 | ```
67 |
68 | This is nothing speciall - just a thin wrapper arround `ConcurrentQueue` from the .NET library. The `RpsServer` will `append` to the `context` while the `RpsEventHandler` `pop`s the `context`.
69 |
70 | A more interesting class to look at is probably `RpsEventHandler`:
71 |
72 | ```python
73 | class RpsEventHandler(IExternalEventHandler):
74 | def __init__(self, contexts):
75 | self.contexts = contexts
76 | self.handlers = {
77 | 'schedules': get_schedules
78 | # add other handlers here
79 | }
80 |
81 | def Execute(self, uiApplication):
82 | while self.contexts:
83 | context = self.contexts.pop()
84 | request = context.Request
85 | parts = request.RawUrl.split('/')[1:]
86 | handler = parts[0] # FIXME: add error checking here!
87 | args = parts[1:]
88 | try:
89 | rc, ct, data = self.handlers[handler](args, uiApplication)
90 | except:
91 | traceback.print_exc()
92 | rc = 404
93 | ct = 'text/plain'
94 | data = 'unknown error'
95 | response = context.Response
96 | response.ContentType = ct
97 | response.StatusCode = rc
98 | buffer = Encoding.UTF8.GetBytes(data)
99 | response.ContentLength64 = buffer.Length
100 | output = response.OutputStream
101 | output.Write(buffer, 0, buffer.Length)
102 | output.Close()
103 |
104 | def GetName(self):
105 | return 'RpsHttpServer'
106 | ```
107 |
108 | The `Execute` method here does the grunt work of working with the .NET libraries and delegating requests to the specific handlers. You can extend this class can by adding new handlers to it. In fact, you don't even need to extend the class to add handlers - just register them in the `handlers` dictionary.
109 |
110 | Each handler takes a list of path elements and a `UIApplication` object. The handler runs in the Revit API context. It should return an HTTP error code, a content type and a string containing the response.
111 |
112 | An example of such a handler is `get_schedules`:
113 |
114 | ```python
115 | def get_schedules(args, uiApplication):
116 | '''add code to get a specific schedule by name here'''
117 | print 'inside get_schedules...'
118 | from Autodesk.Revit.DB import ViewSchedule
119 | from Autodesk.Revit.DB import FilteredElementCollector
120 | from Autodesk.Revit.DB import ViewScheduleExportOptions
121 | import tempfile, os, urllib
122 |
123 | doc = uiApplication.ActiveUIDocument.Document
124 | collector = FilteredElementCollector(doc).OfClass(ViewSchedule)
125 | schedules = {vs.Name: vs for vs in list(collector)}
126 |
127 | if len(args):
128 | # export a single schedule
129 | schedule_name = urllib.unquote(args[0])
130 | if not schedule_name.lower().endswith('.csv'):
131 | # attach a `.csv` to URL for browsers
132 | return 302, None, schedule_name + '.csv'
133 | schedule_name = schedule_name[:-4]
134 | if not schedule_name in schedules.keys():
135 | return 404, 'text/plain', 'Schedule not found: %s' % schedule_name
136 | schedule = schedules[schedule_name]
137 | fd, fpath = tempfile.mkstemp(suffix='.csv')
138 | os.close(fd)
139 | dname, fname = os.path.split(fpath)
140 | opt = ViewScheduleExportOptions()
141 | opt.FieldDelimiter = ', '
142 | schedule.Export(dname, fname, opt)
143 | with open(fpath, 'r') as csv:
144 | result = csv.read()
145 | os.unlink(fpath)
146 | return 200, 'text/csv', result
147 | else:
148 | # return a list of valid schedule names
149 | return 200, 'text/plain', '\n'.join(schedules.keys())
150 | ```
151 | When you write your own handler functions, make sure to implement the function signature: `rc, ct, data my_handler_function(args, uiApplication)`.
152 |
153 | In `get_schedules`, a `FilteredElementCollector` is used to find all
154 | `ViewSchedule` instances in the currently active document. Using a [dict
155 | comprehension](https://www.python.org/dev/peps/pep-0274/) is a nifty way to quickly make a lookup table for checking the arguments.
156 |
157 | The `args` parameter contains the components of the url after the first part,
158 | which is used to select the handler function. So if the requested URL were,
159 | say, `http://localhost:8080/schedules`, then `args` would be an empty list. In
160 | this case, we just return a list of valid schedule names, one per line - see
161 | the `else` at the bottom of the function.
162 |
163 | If the URL were, say `http://localhost:8080/schedules/My%20Schedule%20Name`,
164 | then the `args` list would contain a single element, `"My%20Schedule%20Name"`.
165 | The `%20` encoding is a standard for URLs and is used to encode a space
166 | character. We use `urllib` to unquote the name.
167 |
168 | In order to make the function work nicely with a browser, it is nice to have a
169 | `.csv` ending to it - we redirect to the same URL with a `.csv` tacked on if it
170 | is missing! The code for handling the redirect can be found in the [full sample
171 | script on GitHub](https://github.com/daren-thomas/rps-sample-scripts/blob/master/StartupScripts/RpsHttpServer/rpshttpserver.py).
172 | Notice how the HTTP return code 302 is used as the return value for `rc` - you
173 | can [look up all the HTTP return codes
174 | online](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html), we will only
175 | be using [200 (OK)](https://en.wikipedia.org/wiki/HTTP_200), [302 (Found - used for redirects)](https://en.wikipedia.org/wiki/HTTP_302) and [404 (Not Found)](https://en.wikipedia.org/wiki/HTTP_404).
176 |
177 | Next, the script checks to make sure the schedule name is a valid schedule in the document. A 404 return code is used to indicate an error here.
178 |
179 | The actual code for returning a schedule makes use of a technique described in
180 | Jeremy Tammik's blog post [The Schedule API and Access to Schedule
181 | Data](http://thebuildingcoder.typepad.com/blog/2012/05/the-schedule-api-and-access-to-schedule-data.html).
182 | The `ViewSchedule.Export` method is used to write the schedule to a temporary
183 | file in CSV format and then read back into memory before deleting the file on
184 | disk. This is a bit of a hack and coming up with a better solution is left as
185 | an exercise for the reader...
186 |
187 | The final piece in our puzzle is the `RpsServer`:
188 |
189 | ```python
190 | class RpsServer(object):
191 | def __init__(self, externalEvent, contexts, port=8080):
192 | self.port = port
193 | self.externalEvent = externalEvent
194 | self.contexts = contexts
195 |
196 | def serve_forever(self):
197 | try:
198 | self.running = True
199 | self.listener = HttpListener()
200 | prefix = 'http://localhost:%i/' % self.port
201 | self.listener.Prefixes.Add(prefix)
202 | try:
203 | print 'starting listener', prefix
204 | self.listener.Start()
205 | print 'started listener'
206 | except HttpListenerException as ex:
207 | print 'HttpListenerException:', ex
208 | return
209 | waiting = False
210 | while self.running:
211 | if not waiting:
212 | context = self.listener.BeginGetContext(
213 | AsyncCallback(self.handleRequest),
214 | self.listener)
215 | waiting = not context.AsyncWaitHandle.WaitOne(100)
216 | except:
217 | traceback.print_exc()
218 |
219 | def stop(self):
220 | print 'stop()'
221 | self.running = False
222 | self.listener.Stop()
223 | self.listener.Close()
224 |
225 | def handleRequest(self, result):
226 | '''
227 | pass the request to the RevitEventHandler
228 | '''
229 | try:
230 | listener = result.AsyncState
231 | if not listener.IsListening:
232 | return
233 | try:
234 | context = listener.EndGetContext(result)
235 | except:
236 | # Catch the exception when the thread has been aborted
237 | self.stop()
238 | return
239 | self.contexts.append(context)
240 | self.externalEvent.Raise()
241 | print 'raised external event'
242 | except:
243 | traceback.print_exc()
244 | ```
245 |
246 | This class implements the `serve_forever` function that starts an
247 | `HttpListener` on a specified port and uses `handleRequest` to pass any
248 | requests on to the external event for processing inside the Revit API context.
249 |
250 | [Check the rpshttpserver.py example on GitHub.](https://github.com/daren-thomas/rps-sample-scripts/blob/master/StartupScripts/RpsHttpServer/rpshttpserver.py)
251 |
--------------------------------------------------------------------------------
/where_to_learn_python.md:
--------------------------------------------------------------------------------
1 | # Where to learn Python
2 |
3 | * [Dive Into Python](http://www.diveintopython.net/)
4 | * [Learn Python the Hard Way](https://learnpythonthehardway.org/book/)
5 | * [CodeAcademy](https://www.codecademy.com/learn/python)
6 |
7 |
--------------------------------------------------------------------------------
/where_to_learn_revit_api.md:
--------------------------------------------------------------------------------
1 | # Where to learn the Revit API
2 |
3 | * [Building Coder Blog](http://thebuildingcoder.typepad.com/)
4 | * [Revit API Forum](http://forums.autodesk.com/t5/revit-api/bd-p/160)
5 | * [Stack Overflow: revit-api](http://stackoverflow.com/questions/tagged/revit-api/)
6 | * [Nathan's Revit API Notebook](http://wiki.theprovingground.org/revit-api)
7 |
8 | ### Autodesk
9 | * [Autodesk Developer Network](http://usa.autodesk.com/adsk/servlet/index?siteID=123112&id=2484975)
10 | * [2017: Developer's Guide](http://help.autodesk.com/view/RVT/2017/ENU/?guid=GUID-F0A122E0-E556-4D0D-9D0F-7E72A9315A42)
11 | * [2016: Developer's Guide](http://help.autodesk.com/view/RVT/2016/ENU/?guid=GUID-F0A122E0-E556-4D0D-9D0F-7E72A9315A42)
12 | * [2015: Developer's Guide](http://help.autodesk.com/view/RVT/2015/ENU/?guid=GUID-F0A122E0-E556-4D0D-9D0F-7E72A9315A42)
13 | * [2014: Developer's Guide](http://help.autodesk.com/view/RVT/2014/ENU/?guid=GUID-F0A122E0-E556-4D0D-9D0F-7E72A9315A42)
14 |
15 | ### Online API Documentation
16 | * [Revit API Docs](http://www.revitapidocs.com/)
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------