momentesf087ce6867 asks if we can “select objects by picking subcategory”?
If that means finding elements while working it the Family Editor, where the GenericForm class (Extrusion, Sweep, Blend, etc) have a Subcategory property, then yes we can do that.
Run this macro after selecting an element (such as an extrusion in with the Frame/Mullion subcategory in a Window family) and all the other Frame/Mullion elements will get selected too.
public void SelectBySubCat()
{
var uidoc = this.ActiveUIDocument;
var doc = uidoc.Document;
var forms = uidoc.Selection.GetElementIds()
.Select(q => doc.GetElement(q))
.Where(q => q is GenericForm)
.Cast<GenericForm>();
var sameSc = new List<ElementId>();
foreach (var form in forms)
{
sameSc.AddRange(
new FilteredElementCollector(doc)
.OfClass(typeof(GenericForm))
.Cast<GenericForm>()
.Where(q => q.Subcategory.Id == form.Subcategory.Id)
.Select(q => q.Id));
}
uidoc.Selection.SetElementIds(sameSc);
}
For Joel who said “I’d love to have a button to turn off annotations for all links in a view!”, the Revit API is getting closer to being able to do this but isn’t quite all the way there yet.
The “RevitLinkGraphicsSettings” class was introduced in the 2024 API and allowed us to set the LinkedViewId.
Revit 2025 added many more methods
So now you can use the API to get and set everything here on the “Basics” tab.
Here is some code to set the linked view, color fill, and discipline
var linkInstances = new FilteredElementCollector(doc)
.OfClass(typeof(RevitLinkInstance))
.Cast<RevitLinkInstance>();
using (var tr = new Transaction(doc, "d"))
{
tr.Start();
foreach (var linkInstance in linkInstances)
{
var view = new FilteredElementCollector(linkInstance.GetLinkDocument())
.OfClass(typeof(ViewPlan))
.FirstOrDefault(q => q.Name == "L1NoAnnotations");
var gs = new RevitLinkGraphicsSettings
{
LinkVisibilityType = LinkVisibility.Custom,
LinkedViewId = view.Id,
ColorFill = LinkVisibility.ByLinkView
};
gs.SetDiscipline(LinkVisibility.Custom, ViewDiscipline.Mechanical);
doc.ActiveView.SetLinkOverrides(linkInstance.Id, gs);
}
tr.Commit();
}
But that looks like as far as we can go. If your goal is to turn off this checkbox, we still need to wait for Autodesk to expose that functionality to the API
With all the AI chatbot natural language queries that are so cool these days, is there anyone still with wishes for some Revit API proof-of-concepts written in good old-fashioned C#?
If so, send your questions, suggestions, and wishes for things that might be possible to achieve with the Revit API & a handful will be selected, implemented, and shared here in future posts.
A post in the forum mentions that “I have a list of Walls and I want to create a new View3D with the same walls but containing only the ‘Structure’ function layers”. We can do this nicely with the Part functionality and the Revit API.
publicvoid ShowWallStructureOnly()
{
var doc = this.ActiveUIDocument.Document;
View3D view;
using (Transaction t = new Transaction(doc, "create wall structure view"))
{
t.Start();
view = View3D.CreateIsometric(doc, new FilteredElementCollector(doc)
.OfClass(typeof(ViewFamilyType))
.Cast<ViewFamilyType>()
.First(q => q.ViewFamily == ViewFamily.ThreeDimensional).Id);
if (doc.ActiveView is View3D)
{
view.SetOrientation(((View3D)doc.ActiveView).GetOrientation());
}
view.PartsVisibility = PartsVisibility.ShowPartsOnly;
PartUtils.CreateParts(doc, new FilteredElementCollector(doc, view.Id).OfClass(typeof(Wall))
.Where(q => PartUtils.IsValidForCreateParts(doc, new LinkElementId(q.Id)) && !PartUtils.HasAssociatedParts(doc, q.Id))
.Select(q => q.Id).ToList());
doc.Regenerate();
var toHide = new List<ElementId>();
foreach (var part innew FilteredElementCollector(doc, view.Id).OfClass(typeof(Part)))
{
// I expected DPART_ORIGINAL_TYPE would store the element id of the wall type// but it stores the wall type's name as a stringvar typeName = part.get_Parameter(BuiltInParameter.DPART_ORIGINAL_TYPE).AsString();
var wallType = new FilteredElementCollector(doc)
.OfClass(typeof(WallType))
.Cast<WallType>()
.First(q => q.Name == typeName);
var layers = wallType.GetCompoundStructure().GetLayers();
var parameter = part.get_Parameter(BuiltInParameter.DPART_LAYER_INDEX);
if (parameter == null)
{
continue;
}
// I expected DPART_LAYER_INDEX would store the index as an integer// but it stores it as as stringvar layerInt = int.Parse(parameter.AsString());
var layer = layers[layerInt - 1];
if (layer.Function != MaterialFunctionAssignment.Structure)
{
toHide.Add(part.Id);
}
}
if (toHide.Any())
view.HideElements(toHide);
t.Commit();
}
this.ActiveUIDocument.ActiveView = view;
}
An enhancement to this code could be to check the function of each wall type layer once, not once per wall.
“Can a Curtain Wall grid be generated based on model lines or detail lines in elevation: At the moment we can create custom grid patterns by using type parameters, however, it has its limitations, as often I need to copy or create additional CW grids to create a defined pattern. (example: additional horizontal CW grid for plenum space, etc)”
Cillian asked “Would it be possible to duplicate an existing Area scheme, along with their associated area boundaries and areas?”
We can do all of that, with a limitation that the API does not have the “Apply Area Rules” option that exists in the Revit UI. So whether we copy the existing boundary lines into the new Area Plan, or create new ones, they will not follow the area rules. But Cillian, who is working in Finland, does not use those rules anyway, so here is the new tool that is hopefully useful to Cillian and some other people too!
Simon posted in the forum looking for “the possibility to convert a floor to a ceiling by sketch or a roof by sketch and vice versa”. Yes, this can be done with the Revit API! You can find the source code here.
“Would it be possible to convert Room boundaries to Area boundaries and vice versa. Also possible to convert any line type to Area or/and Room boundary?”
Yes, we can do that with the Revit API.
Select rooms to create area lines from the room boundaries
Select area lines to create room separation lines using the same curves
Select model lines in an area plan view to create area lines
Select model lines in a non-area plan to create room separation lines
Find the code here & keep sending those API wishes!
Starting the week of #AU2023 #Revit API wishes, Peter made a reasonable suggestion that unfortunately can’t be implemented with the Revit API.
“Sub Categories for scope boxes, so I can have different colours, for the different scales, and they can be turned of at the different levels, or the ability to filter them”
The API does support creating sub-categories
var doc = this.ActiveUIDocument.Document;
using (var t = new Transaction(doc, "x"))
{
t.Start();
var wallCat = doc.Settings.Categories.Cast<Category>().FirstOrDefault(q => q.Id.IntegerValue == (int)BuiltInCategory.OST_Walls);
doc.Settings.Categories.NewSubcategory(wallCat, "Wall Subcategory");
t.Commit();
}
But the restriction preventing creating a Scope Box subcategory does not sit at the level of the Revit UI. It is deeper than that, which we can see by checking “CanAddSubcategory”
or by running this code
var scopeBoxCat = doc.Settings.Categories.Cast<Category>()
.FirstOrDefault(q => q.Id.IntegerValue == (int)BuiltInCategory.OST_VolumeOfInterest);
using (var t = new Transaction(doc, "x"))
{
t.Start();
doc.Settings.Categories.NewSubcategory(scopeBoxCat, "Scope Box Subcategory");
t.Commit();
}
which results in a “this category can not add subcategory” exception
Reviving an old tradition from RTC days of the past, this week during Autodesk University send your questions, suggestions, and wishes for things that might be possible to achieve with the Revit API. A handful will be selected, implemented, and shared here in future posts.
In a post at the Revit Architecture Forum, Jesse explained that his firm uses an issue list schedule to show issuances and what sheet are included within that issuance. But this is a manual process where employees have to input the dots by hand. Lots of effort and human error.
Looking for an automated and less error-prone solution, he asks “when we put a revision cloud on a sheet, the revision schedule will communicate with our issue list and automatically add a dot to make it automatic and remove human error? “
Happily, this is a great use of the Revit API and Dynamic Model Update which, in this case, we will use to set parameter values when Revision Clouds are created, modified, and deleted.
The previous two posts have talked about cleaning up a journal file to remove extraneous data and looking at the Revit UI and journal file output to make educated guesses about how the journal file can be modified to become a more dynamic VBScript file that can do more than the journal file that was created in the initial Revit session.
To wrap this up, first we can remove some more of the unneeded lines from the journal file in this post. Also worth noting that the underscore character (_) is the line continuation character in VBScript. You can remove these and put the multiple lines on a single line of text if that makes it more readable for you. We don’t need these:
Jrn.Data _ “DroppedMouseAction” , “no active editor”
Next, we want to create a For loop around the portion of the journal file that does the group export.
Jrn.Command “Ribbon” , “Save a loaded group , ID_SAVE_GROUP”
Jrn.Data _
“Save Group File Name” , “..\..\..\..\..\..\Documents\Same as group name.rvt”
Jrn.Data _
“Save Group Index” , “0”
Jrn.Data _
“Save Group Include Attached” , “1”
Jrn.Data _
“Transaction Successful” , “Create Type Previews”
Jrn.Data _
“Transaction Successful” , “Save a loaded group”
Jrn.Data _
“Transaction Successful” , “Save a loaded group”
Finally, I wanted to create some variables to store a few key pieces of information that would be changed for different RVT files, just so that it is easier to make these changes without messing up something else in the file
'
Dim Jrn
Set Jrn = CrsJournalScript
Dim fileName, exportFolder, numberOfGroupsInFile
' DO NOT CHANGE ANYTHING ABOVE THIS LINE ------------
fileName = "C:\Program Files\Autodesk\Revit 2024\Samples\Snowdon Towers Sample Architectural.rvt"
exportFolder = "C:\Users\harry\Documents\GroupExport\"'
numberOfGroupsInFile = 11
' DO NOT CHANGE ANYTHING BELOW THIS LINE ------------
Jrn.Command "Ribbon" , "Open an existing project , ID_REVIT_FILE_OPEN"
Jrn.Data "File Name" , "IDOK" , fileName
For i = 0 to numberOfGroupsInFile - 1
Jrn.Command "Ribbon" , "Save a loaded group , ID_SAVE_GROUP"
Jrn.Data "Save Group File Name" , exportFolder & "Same as group name.rvt"
Jrn.Data "Save Group Index" , i
Jrn.Data "Save Group Include Attached" , "1"
Jrn.Data "Transaction Successful" , "Create Type Previews"
Jrn.Data "Transaction Successful" , "Save a loaded group"
Jrn.Data "Transaction Successful" , "Save a loaded group"
Next
Jrn.Command "Internal" , "Quit the application; prompts to save projects , ID_APP_EXIT"
Drag and drop that onto your Revit shortcut and get this beautiful result!
The first thing to do, is take the cleaned journal file created in the previous post and see if it will run in Revit. So drag the .txt file onto the Revit shortcut on your desktop and see what happens.
Hmmm. That’s not great. So open up the newly created journal file and search for “journal file playback”
“Data missing from file follows” means that when the journal was being replayed, something happened that was not found in the journal file. In this case, it is the task dialog informing us that there is an existing file and asking if it should be replaced.
Which is correct, that didn’t occur when I first recorded the journal file and now it does occur because that file does exist. This is an important lesson about the brittleness of journal file replay. If something happens (or doesn’t happen) that doesn’t match up with what the journal file says should happen, then the journal replay will stop. With the API, we can much more flexibly handle situations like this, but not with journal replay.
So delete the existing group RVT file and try the replay again. This time it works! Revit launches, opens the RVT, saves the group RVT, and the Revit session exits.
The next thing to do is figure out what we are going to modify in the journal to select different groups to save. Because while in the UI there is a “Group to Save” dropdown list that lists the groups by name, the journal file does not name the group in the same way, it instead has a numeric index that represents the group to save.
The next important thing to know is that Revit will run VBScript as part of journal file replay! So read a good article about looping structures in VBScript (like https://www.w3schools.com/asp/asp_looping.asp) and come back for the next post in the series to see what to do next.
The Revit API does not provide access to the “Save As – Group” command, but maybe you have a lot of groups that you want to export. One approach to consider it modifying a journal file to automate the process. This is a somewhat brittle approach that can’t always be used when the API comes up short, but it is nice when it works.
To get started, I opened an RVT, exported a group, and exited from Revit. This creates a journal file in C:\Users\harry\AppData\Local\Autodesk\Revit\Autodesk Revit 2024\Journals
But that journal file has a ton of stuff (it is a 294KB file!) So the first step is to remove all the comments (the lines that start with a single quote ‘ character or some spaces then a ‘) and also lines that start with Jrn.Directive and some other entries that we don’t care about.
Here is a tool to cleanup that journal file and get rid of a lot of stuff that isn’t helpful for this exercise
Put JournalCleaner.exe in the same folder as one or more journal files, run the command, and for each journal file it will create a “cleaned” version of the journal. In this case, we gone from a 2,500 line journal file, to one with only a couple dozen lines!
‘ Dim Jrn Set Jrn = CrsJournalScript Jrn.Data _ “JournalDefaultTemplate” , “Imperial Multi-discipline=$AllUsersAppData\Templates\English-Imperial\Default-Multi-discipline.rte, Metric Multi-discipline=$AllUsersAppData\Templates\English\Default-Multi-Discipline_Metric.rte” Jrn.Data _ “JournalDefaultViewDiscipline” , “Coordination” Jrn.Command “Internal” , “Display Profile Dialog , ID_DISPLAY_PROFILE_DIALOG” Jrn.Command “Internal” , ” , ID_REVIT_MODEL_BROWSER_OPEN” Jrn.Command “Ribbon” , “Model Browser , ID_REVIT_MODEL_BROWSER” Jrn.Command “Ribbon” , “Open an existing project , ID_REVIT_FILE_OPEN” Jrn.Data _ “File Name” , “IDOK” , “..\..\..\..\..\..\Documents\groups.rvt” Jrn.Data _ “WorksetConfig” , “Custom” , 0 Jrn.LButtonUp 0 , 453 , 206 Jrn.Data _ “DroppedMouseAction” , “no active editor” Jrn.Command “Ribbon” , “Save a loaded group , ID_SAVE_GROUP” Jrn.Data _ “Save Group File Name” , “..\..\..\..\..\..\Documents\Same as group name.rvt” Jrn.Data _ “Save Group Index” , “0” Jrn.Data _ “Save Group Include Attached” , “1” Jrn.Data _ “Transaction Successful” , “Create Type Previews” Jrn.Data _ “Transaction Successful” , “Save a loaded group” Jrn.Data _ “Transaction Successful” , “Save a loaded group” Jrn.Command “Internal” , “Quit the application; prompts to save projects , ID_APP_EXIT” Jrn.Command “Internal” , ” , ID_REVIT_MODEL_BROWSER_OPEN”
In the next post, we will talk about a bit more manual cleanup to this journal file, and then how to read data from external file and more magic.
This tool is an”oldie but goodie” that lets you create and update a toposurface from a set of model lines. A new version for 2022 was recently requested, so I added it to the Terrific Tool project and created a 2022 build. You can download the current installer here and if you like code, that is here
If you’ve taken a look at the new Revit API or tried to update your apps to the new version, you may have seen a lot of errors about units because of API code that Autodesk deprecated in the 2022 API.
The great Extensible Storage Extension is one example of code that needs to be updated for these changes. I’ve made these updates in the Pull Request on Github and you can take a look at the changes which might help you make the changes in your own code. There are also some examples here.
Also, you can’t use a “switch” statement to check ForgeTypeId values, so the code below needed to be changed to use if/else
Also, when you use the Extensible Storage Extension, there are fields that previously required a “UnitType” to be specified like this:
[Field(UnitType = UnitType.UT_Length)]
public double offset { get; set; }
A reasonable guess at how to update that code would be
[Field(SpecTypeId = SpecTypeId.PipingVelocity.TypeId)] public double Property3 { get; set; }
But this will give you a compilation error “An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type”. TypeId does return a string, but because it is a property and not a “const string” it can’t be used this way. You can’t read SpecTypeId properties in an attribute initializer because they aren’t constant expressions.
To solve this, use the string literal value of the
[Field(SpecTypeId = “autodesk.spec.aec.piping:velocity-2.0.0”)] Public double Property3 { get; set; }
And you can find the string literal value with a bit of code like: TaskDialog.Show(“test”, SpecTypeId.PipingVelocity.TypeId);
The Level Displacer tool has been available for several years on the App Store. This tool allows you to create an exploded view of your model by automating the process of creating displacement sets for all elements on each level of your model. Each displacement set is translated a user-specified incremented value in the X, Y, and Z directions.
Self-publishing these tools is the quickest and easiest way to get them to you, so you can now get this tool as part of the free Boost Your BIM toolset
If you enjoy using this and the other free tools and educational resources from Boost Your BIM, drop us a line and let us know how you’d like to make Revit better. You can support our work on Patreon too!