Using the Revit IFC Export in your own add-on

In September 2011, Autodesk make accessible the Industry Foundation Classes (IFC) exporter for Revit as open source code, licensed through a LGPL v. 2.1 licensing agreement. In 2014, this project evolved to include IFC import as well, and was named “IFC for Revit”.

This source code is available on Source Forge, and everyone can contribute to the part of Revit that deal with IFC.

This source code is now the built-in IFC engine for Revit, and I was told that this engine is also behind the conversion service available through Forge.

When the user clicks on Export to IFC, an even is triggered, and Revit catch this even to run the IFC exporter. This also works when the event is triggered by the Document.Export() used through the Revit API by a third-party add-on, like the one I built last week. This IFC Export engine will then read the Revit model, convert every element into its IFC counterpart, and write the resulting IFC file.

How Revit export to IFC

This IFC engine comes with a special UI, the IFCExporterUI, designed to tap into the capabilities of the open-source IFC exporter to give you more control over the configuration of your export.

There are different add-ons for each Revit version, and each new release of Revit is shipped with the most up-to-date IFC engine available at the time. However, the code for this engine evolve quickly, and you can always download the latest version as an add-on on the Autodesk App Store.

At the time of writing, the landscape looks like this:

Revit Version Built-in IFC Exporter version Latest IFC Exporter version (with the Revit IFC add-on)
2016 Exporter 16.0.490.0 – Default UI Exporter 16.6.0.0 – Alternate UI 16.6.0.0
2017 Exporter 17.0.1081.0 – Alternate UI 17.14.0.0 Exporter 17.4.0.0 – Alternate UI 17.4.0.0
2018 Exporter 18.0.0.420 – Alternate UI 18.0.0.420 Exporter 18.2.0.0 – Alternate UI 18.2.0.0

If you want to know witch version you have, you can check it by opening an IFC file with a text editor and look among the first lines, the FILE_NAME one:

Check you exporter version

I didn’t dig into the subtleties of the different flavors of each Revit version, like R2 and other exotic name, you get the picture.

Last week, I published a Revit add-on to send a model to bimsync. Under the hood, this add-on exports the Revit model to IFC before uploading it to bimsync. As I was building my add-on, I realized that I couldn’t just use the Document.Export() function with whatever IFC export settings Revit could provide me. To be useful, such an add-on must allow the user to set up his own IFC Export configuration.

I look into the code of the IFC Exporter UI to understand how this user interface communicate with the export engine. I found out that you need an IFCExportConfiguration class to use a specific IFC Export configuration. Below is an example of the code needed to export a Revit model to IFC:

//Create an instance of IFCExportOptions
IFCExportOptions IFCOptions = new IFCExportOptions();

//Get an instance of IFCExportConfiguration
IFCExportConfiguration selectedConfig = modelSelection.Configuration;

//Get the current view Id, or -1 if you want to export the entire model
ElementId activeViewId = GenerateActiveViewIdFromDocument(doc);
selectedConfig.ActiveViewId = 
        selectedConfig.UseActiveViewGeometry ? activeViewId.IntegerValue : -1;

//Update the IFCExportOptions
selectedConfig.UpdateOptions(IFCOptions, activeViewId);

string folder = "A path to a folder where you want to save your IFC file";
string name = "the name of your IFC file";

//Export the model to IFC
doc.Export(folder, name, IFCOptions);

You start by creating an instance of the IFCExportOptions found in the Autodesk.Revit.DB namespace. This is standard in all Revit flavors.

Then, you retrieve or create an instance of the IFCExportConfiguration class. This is a class provided by the IFC Export add-on that hold every aspect of your export configuration, from knowing if you want to export the BaseQuanties to the path to the IfcPropertySets mapping files.

You get the Id of the active view and use the UpdateOptions method of IFCExportConfiguration to pass it along with all other properties to the IFCExportOptions.

The IFC Export add-on also provide an IFCExportConfigurationsMap class to get the built-in configurations and store the new ones.

The existing export setups

But where find these IFCExportConfiguration and IFCExportConfigurationMaps classes ?

If the IFC for Revit add-on is installed, you can find these classes in the IFCExporterUIOverride.dll. This dll can be found in your ApplicationPlugins folder, in the IFC plugin install folder (C:\ProgramData\Autodesk\ApplicationPlugins\IFC 2018.bundle\Contents\2018)

Running this on a Revit equipped with the add-on will work nicely, but it will fail miserably when the user doesn’t have the IFC for Revit add-on. In this case, the add-on can’t find the IFCExporterUIOverride.dll containing the IFCExportConfiguration class:

Using IFCExportConfiguration without the add-on

If you have a Revit version with the IFC Exporter UI built in (Revit 2017 or 2018), the IFCExportConfiguration and IFCExportConfigurationMaps classes can be found in the IFCExportUI.dll, located in your Revit installation folder (C:\Program Files\Autodesk\Revit 2018\AddIns\IFCExporterUI). In this case, the features available are the same than our previous case, but are in a different dll.

If you don’t have the IFC Exporter UI at all (Revit 2016 and earlier versions), you must fall back to the basic export options.

In a nutshell, we have to manage these three cases:

Class DLL Revit version
IFCExportConfiguration IFCExporterUIOverride.dll

In C:\ProgramData\Autodesk\ ApplicationPlugins\IFC 2018.bundle\Contents\2018

All Revit version equipped with the IFC for Revit add-on
IFCExportConfiguration IFCExportUI.dll

In C:\Program Files\Autodesk\Revit 2018\AddIns\IFCExporterUI

Revit 2017 and 2018 without the IFC for Revit add-on
IFCExportOptions RevitAPI.dll

In C:\Program Files\Autodesk\Revit 2018

Revit 2016 and earlier without the IFC for Revit add-on

To manage these different cases, I created my own export configuration class, named IFCExportConfigurationCustom. This class handle the configuration of the export and is an interface between my code and the possible IFC export options.

I load in my Visual Studio solution the two dll containing the IFCExportConfiguration class, and using alias, I call a different version of it depending on the installation of the user:

IFCExportUI::BIM.IFC.Export.UI.IFCExportConfiguration
IFCExportUIOverride::BIM.IFC.Export.UI.IFCExportConfiguration

I also handle the vanilla Revit 2016 in my IFCExportConfigurationCustom class, without using any version of the IFCExportConfiguration (which is not available in this version of Revit), using instead the plain old IFCExportOptions built-in into Revit.

By managing the different possibilities of the Revit installation, I am able to make use of the best configuration available to export IFC. I am also able to use directly the Setup configuration interface available in the custom UI, and retrieve these user-defined export setups in my add-on.

Edit IFC Export Setup

If you plan on building your own Revit add-on to upload IFC models to a cloud service, don’t hesitate to look into the source code behind this article, the solution can be found on Github.

By opening the source code of its IFC engine, Autodesk make an underrated effort toward interoperability. Even if it’s obviously not for every Revit user, I think that everyone who rely on the Revit IFC export for its own software should look at this engine to get a full understanding of the possibilities offered by this exporter

 

Revit plugins updates and new features

As usual during this time of the year, I published the new version of my plugins for Revit.

Align

The big improvement this year is the ability to align any type of elements, annotations or tags. I must thank Deyan Nenov and his large contribution to the source code for this new feature.

From now on, you can select any kind of element and align or distribute them evenly. This feature use the bounding box of the element in the view as a reference.

Align or distribute all elements

The Align command is still view-dependent, so using it in section view or in a plan view will not have the same effect on the overall position of a given element. A word of caution however, the align function can be unreliable in a 3D view.

View-dependent align functions

Of course, you can still use it to align or distribute your tags and annotations, and it even works with viewports:

Align viewports

Along with these improvement, Align now fully support Area tags, and a few bugs have been eliminated. You can now use Align even if on tag without a leader, and multi-leader text are now fully supported, and some selection subtlety have been introduced.

Align tags and texts

I haven’t tested it with all categories, if you find something weird, please let me know, I would be happy to fix it.

You can find this new version on the Autodesk App Store.

Room Finishing

Along with the support for Revit 2018, I corrected an issue where a skirting board was created even if the room was not bound by a wall. From now on, Room Finishing will not create a skirting board along room bounding lines.

Support room with room boundary lines

Room Finishing is also available on the App Store.

Time Stamper

The Time Stamper plugin got a few improvements this year along with the usual bug smashing.

You can now choose between applying your time stamp to every element, or just the one visible in the view. This will give you the ability to filter on which element you want to apply a stamp.

You can also choose to keep or override an existing value, this should help you keep the history of a given element.

A few bugs where corrected, making the creation of the four shared parameters far more reliable. Groups are now supported.

Support for groups

Time Stamper is now available here.

These new versions are now available on the Autodesk App Store. Don’t hesitate to report any issue you might find in them or ask for improvement.

These plugins are open source, you can find the source code here, here and here. Feel free to use it on your own project or contribute to these projects.

Building a Forge Web Viewer

Web-based “BIM” solutions are the last big trend, and Autodesk is among the most advanced player in this area with their API offering called Forge.

Autodesk Forge is a product from Autodesk that don’t come with a user interface. Instead, it is designed to be used through other software, and especially web-based solutions. Autodesk Forge is also the technology behind most of the web-based product of Autodesk, like BIM 360 Team, Docs or the A360 Online File Viewer.

The community around these products is growing, and new resources and samples are published on an almost daily basis.

Among them, Augusto Gonzales recently wrote a comprehensive tutorial to build a small web viewer using Autodesk Forge and ASP.NET. Being more of a .NET programmer, I took on this opportunity to learn more about this new product and build my own web-based Forge viewer.

I start by creating an ASP.NET Core MVC Web Application with this tutorial. ASP.NET Core being a web framework developed by Microsoft, you can use your C# skills to “easily” build web application.

I follow the detailed explanations from Augusto Gonzales to send my model to the Autodesk server for conversion, get an GUID back and use it to display my model in the Forge viewer. The explication on the blog are straightforward, and I won’t delve into it. I just had to made some few changes since I am using a different version of the ASP.NET Core framework.

Using technologies from Microsoft, it is easy to publish my application on Azure, the Microsoft cloud hosting solution.

I called the end-result Termite, and it is available here.

The Termite Web Viewer

By default, you see a model of my neighborhood from my last post, but you can also upload your own files. A word of caution, uploading and translating a large model can take quite some time, do not close the windows until your model is displayed on the screen.

I also add some features to the viewer. You can lock the rotation of the view by clicking on the lock icon. I also put in place a very basic section tool. To use the section, click on “Add a section” and select a face in the model to create your section.

Viewer Extensions

These features are built as extension of the Autodesk Forge Viewer, are written in JavaScript and run in the browser of the end user.

The source code is available on GitHub. Seasoned web developer will probably find a lot to correct in my application, but I hope to improve with practice

I still have a lot to learn in this area, so you might expect some other web app in the next few weeks.

Align Tag Update

It is this time of the year again, and I have finally take the time to update Align on the App Store for the new version of Revit.

However, there is more in this than a simple version update, and this new release is packed with improvement, both small and large.

The main change reside in the alignment method. In the previous version of Align Tag, I was using the center point of a given tag as a reference to align tag (either left or right). To improve on the alignment of tags of various sizes, I now use the bounding box of the tag.

AlignSolution

Tags will now properly align themselves along their right or left side, regardless of their size or origin point.

Align

However, if you want something similar to the older version, you can use the new Align Center and Align Midlle commands, which will use the center of the tag as a reference.

This new alignment method is more in line with what can be found on solutions like PowerPoint, or Adobe Illustrator, and will allows you to neatly arrange your tag whatever their size or origin point.

Another important improvement is the long awaited support for Text. You can now align Text along with Tag, using the same command.

While I was at it, I also add support for Keynote tag, Room Tag and Space Tag, basically every tag. The Area Tag is still missing, but can be expected for the next version.

However, this support came at a cost, and I have to drop the support for Revit 2015 and prior. So, if you are still using this version, you will have to keep the old Align plugin.

There is also a handful of small UI improvement that I hope will help you.

Aligned tags are now kept selected after running the command so you can align them in another direction right away.

Your Align commands are also one click closer to you! The interface have been artfully arranged in a new tab to keep every icon directly accessible in the ribbon.

icons1

Under the hood, I have rewrote a large part of the code to support more types of annotation elements, and I hope to be able to use this new framework for more complex manipulations, including in the Arrange Tags function.

Of course, Align Tag is still open source, the entire code can be found on Bitbucket.

This plug-in is already available on the Autodesk App Store. If you like it, don’t hesitate to write a nice comment or add a few stars, it always means so much to me!

Details Items, subcategories and filled regions

I am currently working with multiple detail items to create a set of views for architectural detailing. These Detail Items must comply with a precise standard for color and line weights, and of course, be consistent all over the model.

To implement these requirements, I define a set of subcategories for these detail items. Let see how it works with these two wood windows details.

01-WindowsDetailsComponents

These detail items already contain various subcategories, and I can control the color, weight and patterns of their lines in Visibility Overrides. Here Medium Lines are blue and Light Lines are red. Interesting, but not quite enough.

02-LineTypeOverrride

I open these two detail items, go to Object Styles, and create four new subcategories: Wood, Glass, Sealant and Steel. I also remove the previous subcategories. I can now select all lines in the detail item, and assign corresponding subcategories to these lines. Once these detail items are loaded back in the model, the four Detail Item subcategories are visible in the Graphic Overrides window.

I can now control line color, weight and pattern directly in my model, in the Graphic Overrides window for the current view, or in the Object Styles window to apply these colors to every details in the model.

03-OverideByMaterial

Sadly, there is no corresponding feature for managing filled region. As you can see in the picture above, the wood pattern is still in black.

In order to change pattern or color in a filled region, you have to edit the detail item and change the filled region style in the family. If you want to edit your wood pattern, you have to open every detail items containing wood, and change the pattern in the family.

To fix this, and be able to edit directly filled region style for every detail item, I create a small piece of code. This function matches model filled region types with those inside the detail item. So if I have a filled region in my Detail Item family with the same name than one in my Revit project, this filled region will take the properties of the one in the project.

matchProperties

After running this function, I end up with nicely matching wood pattern, all set up directly in my Revit project.

04-OverideFilledRegion

As usual, you will find the source code for this solution below, I hope it will help you solve your filled region issues.


public partial class ThisApplication
{
	public void MatchFilledRegion()
	{
		Document doc = this.ActiveUIDocument.Document;
		
		//Find all Filled Region Type, and create a dictonary with it
		Dictionary<string,FilledRegionType> modelFilledRegionTypes =
			new FilteredElementCollector(doc).OfClass(typeof(FilledRegionType)).ToElements().Cast<FilledRegionType>().ToDictionary(e => e.Name);
		
		//Find all loaded families
		IList<Element> elements = new FilteredElementCollector(doc).OfClass(typeof(Family)).ToElements();
		
		//Get Detail Item category id.
		ElementId detailItemCategoryId = doc.Settings.Categories.get_Item(BuiltInCategory.OST_DetailComponents).Id;
		

		
		//Loop on all loaded families
		foreach (Element familyElement in elements) {
			
			Family family = familyElement as Family;
			
			//Exit the families loop if it isn't a Detail Item Familly
			if (family.FamilyCategory.Id != detailItemCategoryId) continue;

			//Open the family
			Document familyDoc = doc.EditFamily(family);
			string familyPath = Path.Combine(Path.GetTempPath(),family.Name+".rfa");
			
			bool familyEdited = false;
			
			//Find all Filled Region Type in the family
			IList<Element> filledRegionTypes = new FilteredElementCollector(familyDoc).OfClass(typeof(FilledRegionType)).ToElements();
			
			using (Transaction famTx = new Transaction(familyDoc))
			{
				famTx.Start("Edit Filled Region Type");
				
				//Loop on all Filled Region types in the family
				foreach (Element filledRegionTypeElement in filledRegionTypes)
				{
					FilledRegionType filledRegionType = filledRegionTypeElement as FilledRegionType;
					if (modelFilledRegionTypes.ContainsKey(filledRegionType.Name))
					{
						FilledRegionType refFilledRegion = modelFilledRegionTypes[filledRegionType.Name];
						//Change the color
						if (filledRegionType.Color.Red != refFilledRegion.Color.Red
						    || filledRegionType.Color.Blue != refFilledRegion.Color.Blue
						    || filledRegionType.Color.Green != refFilledRegion.Color.Green)
						{
							filledRegionType.Color = refFilledRegion.Color;
							familyEdited = true;
						}
						
						//Change the Background
						if (filledRegionType.Background != refFilledRegion.Background)
						{
							filledRegionType.Background = refFilledRegion.Background;
							familyEdited = true;
						}
						
						//Change line weight
						if (filledRegionType.LineWeight != refFilledRegion.LineWeight)
						{
							filledRegionType.LineWeight = refFilledRegion.LineWeight;
							familyEdited = true;
						}
						
					}
					
				}
				
				famTx.Commit();
			}
			
			if (familyEdited)
			{
				familyDoc.LoadFamily(doc, new FamilyOption());
				familyDoc.Close( false );
			}
			else
			{
				familyDoc.Close( false );
			}
		}
	}
}

public class FamilyOption : IFamilyLoadOptions
{
	public bool OnFamilyFound(bool familyInUse,out bool overwriteParameterValues)
	{
		overwriteParameterValues = true;
		return true;
	}
	
	public bool OnSharedFamilyFound(Family sharedFamily,bool familyInUse, out FamilySource source,out bool overwriteParameterValues )
	{
		source = FamilySource.Family;
		overwriteParameterValues = true;
		return true;
	}
}

Some Thoughts About Rooms and Spaces

A lot of MEP calculations available in Revit needs analytical spaces at some point, and it is crucial to have them properly modeled.

Properly placing these spaces can be a tedious business, especially for large buildings with hundreds of rooms. You can use the Place Space Automatically function, but it can create spaces in undesired places, and does not necessarily match the architectural rooms.

Furthermore, superposing of a space upon an existing architectural room only retrieves the name and the number of the corresponding room, and falls short for any other property we could want to add to our MEP space.

One of the solutions for creating MEP spaces is to match rooms created in the linked architectural model. To do so, I wrote a small application for creating a space for each room defined in a linked model.

This application loops on every linked instance, searching for rooms, and uses these rooms to create the corresponding space, retrieving in the process a handful of parameters, like name, number, and most critically, Limit Offset.

You can find here a little ScreenCast showing how it works. The entire source code is available below.

While working on it, I also found some interesting property : Computation Height. This is a level property, and it allows to define the height used to define boundaries on this level.

Let’s create three rooms on a given level:

ThreeRooms

But if we add some variation on the floor level, the room disappears with the following warning:Warning

ARoomDisappear

By default, these rooms are calculated at the level elevation. Every wall at “0 m” above the level will be used as a room boundary.

The Computation Height properties allows us to change the elevation where we calculate the room. In our example, we change the Computation Height of the Level 1 to 1 meter, and the room fit nicely between its boundaries.

ComputationHeight

Of course, this is also true for Spaces.

public void RoomToSpace()
{
	Document activeDocument = this.ActiveUIDocument.Document;
	
	//Get All linked instance
	FilteredElementCollector collector = new FilteredElementCollector(activeDocument);
	List<RevitLinkInstance> linkInstances = collector.OfCategory(BuiltInCategory.OST_RvtLinks).WhereElementIsNotElementType().ToElements().Cast<RevitLinkInstance>().ToList();
	
	//Get all levels
	collector = new FilteredElementCollector(activeDocument);
	List<Level> levels = collector.OfCategory(BuiltInCategory.OST_Levels).WhereElementIsNotElementType().ToElements().Cast<Level>().ToList();
	
	using (Transaction tx = new Transaction(activeDocument)) {
tx.Start("Create Spaces");

//Loop on all linked instance
foreach (RevitLinkInstance linkInstance in linkInstances) {
	
	//Get linked document
	Document linkedDocument = linkInstance.GetLinkDocument();
	
	//Get linked instance position
	Transform t = linkInstance.GetTotalTransform();
	
	//Get rooms in the linkedDocument
	collector = new FilteredElementCollector(linkedDocument);
	List<Room> linkedRooms = collector.OfCategory(BuiltInCategory.OST_Rooms).ToElements().Cast<Room>().ToList();
	
	//Create a space for each room
	foreach (Room room in linkedRooms) {
LocationPoint locationPoint = room.Location as LocationPoint;
XYZ roomLocationPoint = locationPoint.Point;
roomLocationPoint = t.OfPoint(roomLocationPoint);

if (roomLocationPoint != null)
{
	Level level = GetNearestLevel(roomLocationPoint,levels);
	UV uv = new UV(roomLocationPoint.X, roomLocationPoint.Y);
	
	Space space = activeDocument.Create.NewSpace(level,uv);
	
	space.Number = room.Number;
	space.Name = room.Name;

	Parameter limitOffset = space.get_Parameter(BuiltInParameter.ROOM_UPPER_OFFSET);
	limitOffset.Set(room.get_Parameter(BuiltInParameter.ROOM_UPPER_OFFSET).AsDouble());
}
	}
}

tx.Commit();
	}
	
}

private Level GetNearestLevel(XYZ point,List<Level> levels)
{
	Level nearestLevel = levels.FirstOrDefault();
	double delta = Math.Abs(nearestLevel.ProjectElevation - point.Z);
	
	foreach (Level currentLevel in levels) {
if (Math.Abs(currentLevel.ProjectElevation - point.Z) < delta) {
	nearestLevel = currentLevel;
	delta = Math.Abs(currentLevel.ProjectElevation - point.Z);
}
	}
	
	return nearestLevel;
}

Model Timestamp

As we receive models from subcontractors or partners, we need to integrate them in a coordination model.

The coordination model files structure look like this.filestructure

In the coordination model, we use linked views and model specific overrides to fine tune model display. To keep these settings when a linked model is updated, we just override the previous liked file with its new version. This process implies to rename the file each time we receive a new version from a subcontractor. So when we receive a file named with a date or a version, we rename it along some quality control checks.

process

But we also have to follow which model version we are linking in our coordination model. Renaming files is great to keep the link alive, but we lost the original name in the process.

To keep track of the version of the linked file, I create some kind of timestamp on every object of a given model. This application writes version information on four shared parameters, common to every object.

Once in the coordination model, these shared parameters allows us to know from which version a given element came from.

IdentificationData

They can also be used to create filters to highlight the origin of each element in a view.

I also find some very interesting side effects. For example, I create a linked models schedule with a multi-category schedule displaying only the four shared parameters.

LinkedModelSchedules

My only concern is the performance of such an application. I run it on the Revit MEP example file, and it take 31 seconds, regeneration included. It could easily handle a larger model, but the user will then need some patience as the application run.

You will find below a piece of code I use to write values on every elements of the model. This code does not include any interface, but I hope to be able to publish a packaged version anytime soon.

 

public void ModelTimeStamp()
{
	Document doc = this.ActiveUIDocument.Document;
	
	using (Transaction tx = new Transaction(doc)) {

		tx.Start("Model TimeStamp");

		//Create a list of category
		CategorySet myCategories = CreateCategoryList(doc, this.Application);

		//Retrive all model elements
		FilteredElementCollector collector = new FilteredElementCollector(doc);
		IList<ElementFilter> categoryFilters = new List<ElementFilter>();

		foreach (Category category in myCategories)
		{
			categoryFilters.Add(new ElementCategoryFilter(category.Id));
		}

		ElementFilter filter = new LogicalOrFilter(categoryFilters);

		IList<Element> elementList = collector.WherePasses(filter).WhereElementIsNotElementType().ToElements();

		//Add the value to all element
		if (elementList.Count > 0)
		{
			foreach (Element e in elementList)
			{
				WriteOnParam("Date", e, DateTime.Now.ToShortDateString());
				WriteOnParam("Version", e, "First Release");
				WriteOnParam("FileName", e, "SubContractors Model");
				WriteOnParam("Trade", e, "HVAC");
			}
		}

		tx.Commit();
	}

}

private void WriteOnParam(string paramName, Element e, string value)
{
	IList<Parameter> parameters = e.GetParameters(paramName);
	if (parameters.Count != 0)
	{
		Parameter p = parameters.FirstOrDefault();
		if (!p.IsReadOnly)
		{
			p.Set(value);
		}
	}
}

private CategorySet CreateCategoryList(Document doc, Autodesk.Revit.ApplicationServices.Application app)
{
	CategorySet myCategorySet = app.Create.NewCategorySet();
	Categories categories = doc.Settings.Categories;

	foreach (Category c in categories)
	{
		if (c.AllowsBoundParameters && c.CategoryType == CategoryType.Model)
		{
			myCategorySet.Insert(c);
		}
	}

	return myCategorySet;
}

Revit categories and classification systems

Standards for classifying building elements have been around for some times, but building information modeling gives us new perspectives for using them.

There is a handful of these classifications currently in use. The Construction Specifications Institute produce the MasterFormat and UniFormat, used in United States and Canada. The Construction Project Information Committee in the United Kingdom provides Uniclass and Uniclass 2. And the “Catalogue des articles normalisés” was used in Switzerland even before computers were able to manage it.

Revit provides two built-in type parameters to manage such classification systems, the Assembly Code and the Assembly Description. These parameters allow us to link any Revit type to an existing classification system.

Assignement

This classification system can be loaded in Revit through the Assembly Code interface.

Assemblycode

 

Autodesk provides us with the Uniformat classification, through the UniformatClassifications.txt. This tab-separated values text file define the classification structure with four columns:

Classification Code - Description - Rank - Revit category Id
  • The Classification Code is the number associated with each item in a given classification. It is linked to the Assembly Code in Revit.
  • The Description is the text associated with each item of the classification. Once we add an Assembly Code to a Revit type, this description appears in the Assembly Desciption.
  • The Rank define the hierarchy of the item in the classification. This allows Revit to display any linked classification in a tree view.
  • Finally, the Revit category Id allows us to create a first mapping between classification items and Revit categories. This allow us to filter by Revit category while assigning Assembly Code.

To create such a mapping, we need the list of Revit categories. To extract this, I run a small routine to write every built-in Revit category to a .csv file. Along the way, I find some interesting properties of these categories. For example, the IsCuttable property list cuttable categories, something I was talking about in a previous post.

But most important here is an exhaustive list of all Revit categories, along with their Id, a prerequisite to create relations between Revit categories and classifications items.

These relations allows us to filter by category while assigning Assembly Codes.

You can find here the .csv file with all Revit categories, along with the code used to create it.

public void Categories()
{
	Document doc = this.ActiveUIDocument.Document;
	Categories categories = doc.Settings.Categories;
	
	List<string> categoriesList = new List<string>();
	categoriesList.Add(
	"Rank;
	CategoryType;
	Name;Id;
	IsSubCategory;
	IsCuttable;
	IsTagCategory");
	
	foreach( Category c in categories )
	{
		categoriesList.Add(
		"1;"+c.CategoryType.ToString()+";"+
		c.Name+";"+c.Id+";"+";"+c.IsCuttable.ToString()+";"+
		c.IsTagCategory.ToString()
		);
		//Retrive sub categories
		foreach (Category subc in c.SubCategories) {
			categoriesList.Add(
			"2;"+subc.CategoryType.ToString()+";"+
			subc.Name+";"+subc.Id+";"+c.Name+";"+
			subc.IsCuttable.ToString()+";"+
			subc.IsTagCategory.ToString()
			);
		}
	}
	
	string path = @"C:\categories.csv";
	File.WriteAllLines(path,categoriesList.ToArray());
}

Grouping clash results

In any given building model, there is a number of issues to be addressed. A large part of these issues are what we call “hard clashes”, when a building component physically penetrate the space occupied by another building component. In these case, two or more building elements compete for the same volume.

Finding these clashes is now quite simple thanks to clash detection software that detect geometrical interferences between elements of a building model.

But after having a hard time finding these issues through coordination sections hand-drawn from plans, we now face the inverse problem and end up with too many clashes in our models. Running a simple clash detection can quickly yield thousands of clashes, making the entire process nearly useless.

Furthermore, a clash only represent a geometrical intersection between two elements, and is more a part of an issue than the issue itself. We have to group these clashes to be able to extract meaningful issues from the meaningless clashes.

Grouping these clashes is generally a manual task, and the user have to run through thousands of clashes to sort them in relevant groups.

While trying to automate this tedious task, I run across the examples provided as part of the Autodesk Navisworks Software Development Kit. These examples include a nice Clash Grouper plugin for Navisworks, enabling various method for grouping clash results.

groupClashes

As an example, I run a clash detection between the blue Selection A and the green Selection B, and get seven clashes, shown here as red dot:

Ungrouped

With the Clash Grouper, I can group these clashes by grid intersection:

groupByGrid

I can also group them by cluster analysis, where we search for the optimum grouping solution given the expected number of groups:

groupByCluster

I also add my own method for grouping clash against a specific set. I use this to group all clash belonging to a single element in one of the two selection sets.

grouplashesEdited

Here, I group by element from the Selection A (blue)

groupBySelectionA

Here, I group by elements from the selection B (green)

groupBySelectionB

If elements from one set are more relevant for the end user, the final clash report is clearer for this user when clashes are grouped against this set.

This plug-in enables a lot of possibilities for sorting clash detection results in a meaningful report, and will become a full-time member of my coordination toolbox.

To install this plug-in, you can copy-paste the ClashDetective.ADSK.dll file available here in a new ClashDetective.ADSK folder in C:\Program Files\Autodesk\Navisworks Manage 2016\Plugins. You can also see my edited version of the example code here.

Shared family

The Shared checkbox in the Revit family editor allows us to use nested families just like the root one.

SharedCheckBox

Checking the Shared checkbox is only useful when this family is nested into another. When you load the root family into your project, Revit will also load the nested one. You will then be able to see it in the Project Browser and in schedules, and your shared family will behave just like any other families, except that it is nested into another.

This function is very useful to insert additional elements upon existing ones, according to specific rules.

As an example, I add a light switch to a door family. This light switch is wall-based, and will appear alongside of every doors in the project. As this light switch is a shared family, these instances appear on the electrical fixture schedule.

Visible

Furthermore, these nested families only appear in schedules if they are visible in the project. I use this property to select on which door I want a light switch. I add a Yes/No parameter on my family to control the visibly of the switch. Once hidden in the project, the switch doesn’t appear in the schedule either.

Hidden

Using shared families is a very efficient way to insert elements in a model, and is a good starting point for rule-based modeling.

But once every light switch families have been inserted in the model through their host, we generally want to be able to adapt the position of some of these elements.

To do so, I wrote a few lines of code to create a copy of every nested light switch directly in the model. These new light switches are no longer nested, and can be easily modified to fit the local configuration. Furthermore, these elements are now electrical fixtures families, and can be added to an electrical circuit to perform load calculations.

Extracted

 

public void ExtractNestedFamillies()
{
	
	UIDocument uidoc = this.ActiveUIDocument;
	Autodesk.Revit.DB.Document doc = uidoc.Document;
	
	//Select a family instance
	FamilyInstance fi = doc.GetElement(
		uidoc.Selection.PickObject(
			ObjectType.Element ).ElementId )
		as FamilyInstance;
	
	// Create a filter to retrive all instance of this family
	List<ElementFilter> filters = new List<ElementFilter>();
	foreach (ElementId symbolId in 
			 fi.Symbol.Family.GetFamilySymbolIds()) 
	{
		filters.Add(new FamilyInstanceFilter(doc,symbolId));
	}
	ElementFilter filter = new LogicalOrFilter(filters);

	// Apply the filter to the elements in the active document
	FilteredElementCollector collector = 
		new FilteredElementCollector(doc);
	ICollection<Element> familyInstances = 
		collector.WherePasses(filter).ToElements();
	
	using (Transaction tx = new Transaction(doc)) {
		
		tx.Start("Extract Nested Familes");
		
		//Loop on all family instances in the project
		foreach (Element element in familyInstances) {
			
			FamilyInstance instance = element as FamilyInstance;
			
			ICollection<ElementId> subElementsIds = 
				instance.GetSubComponentIds();
			
			//Loop on all nested family
			foreach (ElementId id in subElementsIds) {

				Element ee = doc.GetElement(id);
				FamilyInstance f = ee as FamilyInstance;
				
				//The fammily is face based
				if (f.HostFace != null)
				{
					Element host = f.Host;
					Face face = host.GetGeometryObjectFromReference(
						f.HostFace) as Face;
					LocationPoint locPoint = f.Location as LocationPoint;
					doc.Create.NewFamilyInstance(
						face,locPoint.Point,f.HandOrientation,f.Symbol);
				}
				//The fammily is host based
				else if (f.Host !=null)
				{
					LocationPoint locPoint = f.Location as LocationPoint;
					Level level = doc.GetElement( f.LevelId) as Level;
					
					FamilyInstance fam = doc.Create.NewFamilyInstance(
						locPoint.Point,f.Symbol,f.Host,
						level,StructuralType.NonStructural);
					
					//Flip the family if necessary
					if (instance.CanFlipFacing)
					{
						if (instance.FacingFlipped) {fam.flipFacing();}
					}
					if (instance.CanFlipHand)
					{
						if (instance.HandFlipped) {fam.flipHand();}
					}
				}
				//The family is point based
				else
				{
					LocationPoint locPoint = f.Location as LocationPoint;
					Level level = doc.GetElement( f.LevelId) as Level;
					doc.Create.NewFamilyInstance(
						locPoint.Point,f.Symbol,level,
						StructuralType.NonStructural);
				}
			}

		}
		
		tx.Commit();
	}

}