Create a 3d view showing only #Revit wall structural layers

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.

public void 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 in new 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 string
            var 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 string
            var 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.

Recursively Merge Subcategories – Approximately 750% useful

The feeback on last week’s post was positive

And I moved some code around so that it would recursively handle all nested subfamilies

       private void MergeFamilies(Document doc, ElementId categoryId, List<string> namesOfSubcategoriesToMerge, string newCategoryName)
        {
            List<Family> families = new FilteredElementCollector(doc)
                .OfClass(typeof(Family))
                .Cast<Family>()
                .Where(q => 
                q.FamilyCategoryId == categoryId &&
                q.Name != string.Empty)
                .ToList();
            foreach (var family in families)
            {
                MergeFamilies(family, categoryId, namesOfSubcategoriesToMerge, newCategoryName);
            }
        }
        private void MergeFamilies(Family f, ElementId categoryId, List<string> namesOfSubcategoriesToMerge, string newCategoryName)
        {
            var document = f.Document;
            var famDoc = document.EditFamily(f);
            // update any subfamilies before this family
            MergeFamilies(famDoc, categoryId, namesOfSubcategoriesToMerge, newCategoryName);

And here is the source code and updated installer. Enjoy!

#AU2023 API Wish Granted! Use Lines to Create Curtain Grids

“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)”

Yes – DONE!

Code here

AU2023 API Wish – Duplicate Area Scheme, Areas, Boundary Lines, and Tags

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!

Create a floor, roof, ceiling, or lines from another #Revit element’s sketch

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.

#AU2023 API wish – Room/Area tool now even better

Thanks to some feedback from Tim, this new tool can now also be used to create room/area lines when you select one or more:

  • Area
  • Area Tag
  • Wall
  • Floor

https://bitbucket.org/BoostYourBIM/boostyourbimterrifictools/src/master/BoostYourBIMTerrificTools/RoomAreaBoundaries/

#AU2023 API wish 1 – Area & Room Line Creation

Thanks Tim for asking

“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.

  1. Select rooms to create area lines from the room boundaries
  2. Select area lines to create room separation lines using the same curves
  3. Select model lines in an area plan view to create area lines
  4. Select model lines in a non-area plan to create room separation lines

Find the code here & keep sending those API wishes!

Sorry, we can’t add a Scope Box subcategory

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

So all we can do is vote for this enhancement in the Ideas Forum and wait for Autodesk to remove this limitation.

What are your #AU2023 #Revit API wishes?

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.

You can find some past wishes that were granted at https://boostyourbim.wordpress.com/category/api-wish/rtc/

Can you tie the Revision Schedule to an Issue List Schedule?

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.

Journal File Magic & Exporting Groups to File (the grand finale)

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 _
    “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.LButtonUp
  • 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 fileName, exportFolder, numberOfGroupsInFile
fileName = "C:\Program Files\Autodesk\Revit 2024\Samples\Snowdon Towers Sample Architectural.rvt"
exportFolder = "C:\Users\harry\Documents\GroupExport\"'
numberOfGroupsInFile = 11

Here’s the finished 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!

Journal File Magic & Exporting Groups to File (part 2)

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.

Journal File Magic & Exporting Groups to File (part 1)

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

https://bitbucket.org/BoostYourBIM/journalcleaner/src/master/JournalCleaner/bin/Release/net6.0/publish/win-x86/ – click on “view raw” to download the file

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.

Topo From Lines now in the free Revit tool set

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

TopoFromLinesScreenShot

Revit 2022: What’s up with ForgeTypeId?

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);

Level Displacer now available for free on all Revit versions

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!

Custom Errors – Preventing Specific Changes to the Revit model

Let’s say there is a specific list of View Scales that you want allowed in your Revit projects. Or certain naming conventions that should be used. Or something else like that where you’d like to automate the process of checking a user’s change and determining if it should be allowed, prevented, or trigger a warning.

This can be done with two pieces of Revit API functionality – Updater & Custom Failures. You can find all the code here and an explanation in the video below.

If you think lessons like there are interesting and helpful, please support Boost Your BIM at Patreon, take one of our video courses on the Revit API, or contact us to discuss how we can work together to make Revit better.

            Utils.ViewScaleUpdater viewScaleUpdater = new Utils.ViewScaleUpdater(application.ActiveAddInId);
            UpdaterRegistry.RegisterUpdater(viewScaleUpdater, true);
            UpdaterRegistry.AddTrigger(
                viewScaleUpdater.GetUpdaterId(),
                new ElementClassFilter(typeof(View)),
                Element.GetChangeTypeParameter(new ElementId(BuiltInParameter.VIEW_SCALE)));

            Utils.illegalViewRangeFailureId = new FailureDefinitionId(Guid.NewGuid());
            FailureDefinition.CreateFailureDefinition(
                Utils.illegalViewRangeFailureId,
                FailureSeverity.Error,
                "This view scale is not allowed.");

A better Select By Type tool

There are a few things you can’t do with Revit’s “Select All Instances” tool

  • You can’t select all instance of one type and then all instances of another type and combine them into a single selection set
  • You can’t select lines
  • You can’t restrict the selection to all instances in the active view

To solve these limitations, try the new FREE Select By Type tool in the Boost Your BIM Terrific Tools.
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 and support our work on Patreon.

Sometimes it’s not you

Here’s a really simple bit of code to create a wall and change the wall type

public void crash()
{
	Document doc = this.ActiveUIDocument.Document;
	WallType wt = new FilteredElementCollector(doc)
		.OfClass(typeof(WallType))
		.Cast<WallType>()
		.FirstOrDefault(q => q.Kind == WallKind.Basic);
	
	using (Transaction t = new Transaction(doc, "test"))
	{
		t.Start();
		Wall wall = Wall.Create(doc, 
            Line.CreateBound(XYZ.Zero, XYZ.BasisX),
            new FilteredElementCollector(doc).OfClass(typeof(Level)).FirstOrDefault().Id,
            false);		
		wall.ChangeTypeId(wt.Id);
		t.Commit();
	}
}

The problem is that when you create a wall with the Revit API, Revit uses the wall type last used when a wall was created with the user interface. If the last wall type used was a basic wall, then everything is fine. But if the last wall type used in the interface was a Curtain Wall…

You can work around this by adding a Document.Regenerate() just before changing the wall type. But it is a good reminder to think about how Revit’s state can affect your add-in, be careful working in the API with newly created elements, and that sometimes by adding a “regenerate” or changing how you are using transactions you can find a solution.