Wall Openings (again)

I am kind of obsessed with wall openings. The entire process of asking a structural engineer for holes in its beloved wall to let ducts and pipes goes through has always been a rather frustrating experience.

After my first article about a semi-automated way for placing an opening family, here is my latest attempt at creating the perfect opening family.

A good opening family must have the following features:

  • hosted on a wall, a slab or a beam
  • visible in a 3D view
  • fully parametric
  • schedulable
  • sharable in its own model
  • a nice 2D representation

Lets explore these features.

To host them, but still keep the ability to share them in their own model, I use the Generic Model Face Base family template to create my opening family. This template allows me place my opening on any wall, slab or beam, even if they are in a linked model.

In this Generic Model, I create the Opening subcategory, where I will place every element of my opening. This will allow me to fine tune the display of my openings in my model.

I create a bunch of reference planes, and drive them with three shared parameters, Width, Height and Depth. These reference planes help me constrain the Void Form that will cut the host. This void form will create an actual hole in the wall or slab, and will allow me to perform accurate clash detections after integrating these openings.

To be able to see my opening in a 3D view, I draw some Model Lines to outline the general shape of the opening, and place them in the “Opening” subcategory. These Model Lines are only visible in 3D.

3D View

The 2D representation is a pretty complex subject, and I have yet to find the perfect solution. After some experiences with Model lines, I have switched to annotations elements. These annotations elements are drawn in two nested families, one for the projection symbol, the other for the cut symbol.

These annotation families are drawn in a Generic Model, with the “Opening” subcategory, in order to follow the same graphical rules than the main family.

I also use Masked Regions to draw filled patterns, and use the Generic Model Override in a plan view to fill them with black. I am not entirely satisfied with this solution, but the workaround involve Detail Items, and I don’t want to deal with two different categories.

PlanSection 1

Section 2

To display the elevation of my opening family in a tag, or a schedule, I create two shared parameter, Top Elevation and Bottom Elevation.

As I was searching for a solution to calculate the elevation, I notice a feature I wasn’t aware of, the “Schedule Level”, present since at least Revit 2015, that allow us to define a reference level. Revit use it to automatically calculate the corresponding elevation. Since this elevation cannot be used directly in a schedule or a tag, I use a Dynamo definition to update elevation values in my shared parameters. This Dynamo definition perform some simple calculation to retrieve Top and Bottom elevations, and send these values in the corresponding shared parameter.

Dynamo

My work with wall openings is far from complete, and subjects like sharing these openings, and managing their modifications are still pending. You will find here the different families, models and Dynamo definition used in this article, feel free to use or improve on them.

Time Stamper, the Add-In

There is no easy way to override the color of an entire Revit link. Since most of my work involves linking Revit model from various subcontractors, this is something I miss badly.

Until recently, I was still using some workset hack to create filters on linked models. These filters were allowing me to display linked model with my color of choice.

But relying on workset leave much to be desired, and I have to find another solution.

I recently came up with a different solution, where each model element know where they came from.

The idea is to add file name, date and version in shared parameters on every model element. I created the corresponding Revit Add-In, and it is now available on the Autodesk App Exchange.

This application will:

  • Ask for identification information
  • Create four shared parameters on every category
  • Fill these shared parameters with identification information

The code itself is pretty easy, but there is a lot of applications.

First, it becomes easy to create filters on linked model. These filters allow us to display each linked model with a different color.

ColorsByModel

But it also enables us to tag the origin of every element, like we can see in the following screenshot.

IdentifyElements

You can create a linked models schedule, with date and version.

LinkedFilesSchedule

To help you create filters and tags with these parameters, you will find here the shared parameter text file. Please also note that the application uses a list of categories to create the four shared parameters. You will find this list here.

I hope this application will help you in your work, don’t hesitate to share your suggestions in the comments.

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

BCF Reader Update

This post is long overdue, but I finally take the time to update my BCF Reader.

BCFReader-Logo_small

First of all, I have tested it on a larger set of BCF files, and I hope these will make it more robust, especially if something is wrong within the BCF file. My experiences include files coming from Tekla BIMSight, Matteo Cominetti’s BCFier and Kubus BIM Collab.

I have to remove the support for Word 97-2003 Documents (*.doc), since the library I use does not support them. I will see how I can integrate them back in a future release.

Among the change, I add a small progress bar to allow you to pour yourself a coffee when dealing with huge quantities of notes.

I also add Status and Verbal Status to the report, just after the date. A more subtle change, the default path to save your report is now the same than the BCF file itself.

I improve the Readme file to include a small explanation on how to use the BCF Reader.

I spotted some problems with styles in the Word template. To be sure to have all of them available in the BCF Reader, you must write a few lines in your Word template, apply your styles to them, then delete them. This will ensure that you have created these styles in your Word template before using it.

Finally, I want to thank Julien Benoit and François Lahouste for their comments and their files.

As usual, you can download the BCF Reader here, and check the code here.

Revit model management in Excel

Model management can involve some tedious tasks. Cleaning up the mess created by an unruly user when he import all categories of an old Revit model is probably the most tedious of these tasks.

When someone import elements from another model, we quickly end up with thousand of view templates, filters, and other user created views which can become totally unmanageable.

Here come the BIM Manager, who spend two tedious days sorting these views and campaigning against view proliferation.

To help address this problem, I create a small piece of code for exporting every view, template and filter to three CSV files.

CSV Files

To read these files in a meaningful way, I use PowerPivot in Excel to create some kind of a small database, with two relationships :

Relationships

We can then create tables displaying how filters and views are used, like how many filters are used, or where the templates are applied.

Filters Usage

Templates Usage

Once loaded in the PowerPivot tool, this data allows us to quickly identify which template or filter are used and delete the unwanted ones.

The entire source code is available below, please feel free to use it for your own projects.

public void ExportViewTemplatesList()
{
	Document doc = this.ActiveUIDocument.Document;
			
	//find all view
	IEnumerable<View> views = 
from elem in new FilteredElementCollector(doc).OfClass(typeof(View))
let view = elem as View
select view;
			
	//Create a text file for exporting
	List<string> lines = new List<string>();
	//Add the first line
	lines.Add("TemplateName");
			
	foreach (View view in views) {
		if (view.IsTemplate)
		{
			lines.Add(view.Name);
		}
	}
			
	lines = lines.Distinct().ToList();
	string exportpath = @"templates.csv";
	File.WriteAllLines(exportpath,lines.ToArray(),Encoding.UTF8);
}

public void ExportFiltersList()
{
	Document doc = this.ActiveUIDocument.Document;
			
	//find all filters
	IEnumerable<ParameterFilterElement> filters = 
from elem in new FilteredElementCollector(doc)
.OfClass(typeof(ParameterFilterElement))
let filter = elem as ParameterFilterElement
select filter;
			
	//Create a text file for exporting
	List<string> lines = new List<string>();
	//Add the first line
	lines.Add("FilterName");
			
	foreach (ParameterFilterElement filter in filters) 
	{
		lines.Add(filter.Name);
	}
			
	lines = lines.Distinct().ToList();
	string exportpath = @"filters.csv";
	File.WriteAllLines(exportpath,lines.ToArray(),Encoding.UTF8);
}

public void ExportViewsList()
{
	Document doc = this.ActiveUIDocument.Document;
			
	//find all view
	IEnumerable<View> views = 
from elem in new FilteredElementCollector(doc).OfClass(typeof(View))
let view = elem as View
select view;
			
	//Create a text file for exporting
	List<string> lines = new List<string>();
	//Add the first line
	lines.Add("ViewName;ViewType;IsTemplate;"
		+"TemplateName;LevelName;FilterName");
			
	foreach (View v in views) 
	{
		Level level = v.GenLevel;	
		string levelName = "";
		if (level != null)
		{
			levelName  =  level.Name;
		}
		
		//Get view filters
		ICollection<ElementId> filterIds;
		try {
			filterIds = v.GetFilters();
		} catch (Autodesk.Revit.Exceptions.InvalidOperationException) {
			filterIds = new List<ElementId>() ;
		}
				
		string templateName = "";
				
		if (v.ViewTemplateId.IntegerValue != -1)
		{
			templateName  =  doc.GetElement(v.ViewTemplateId).Name;
		}
				
		string viewinfos =
			v.ViewName + ";" +
			v.ViewType + ";" +
			v.IsTemplate.ToString()  + ";" +
			templateName  + ";" +
			levelName;
				
		if (filterIds.Count != 0)
		{
			foreach (ElementId filterId in filterIds)
			{
				string filterName = doc.GetElement(filterId).Name;
				string line  = viewinfos   + ";" + filterName;
				lines.Add(line);
			}
		}
		else
		{
			lines.Add(viewinfos);
		}
	}
			
	string exportpath = @"views.csv";
	File.WriteAllLines(exportpath,lines.ToArray(),Encoding.UTF8);
}

Parametric Bridge

Today, I introduce you Louis-Marie Borione, one of my colleague and friend, to talk about his work with Grasshopper for civil engineering.

Hi everyone, I am a BIM Manager in a large infrastructure engineering company in France, and I thank BIM42 to give me the opportunity the present you my work on parametric Bridge.

I recently worked on a preliminary design for a bridge in the Middle East. When starting looking at conceptual design drawings I came with the impression that we would have to make a lot of hypothesis.

Bridge

For example the height above ground at bridge ends is not dimensioned . The idea was to produce any kind of BIM model of this bridge therefore I first thought of using Rhinoceros and Grasshopper.

So I listed all of my hypothesis:

GH

And I started building the wireframe model:

Rhino

And the final model:

FinalModel

Then I had to produce cross sections, longitudinal sections and plan views. I tried to do it within Rhinoceros but the result wasn’t good enough. Additionally some cross beam had a different shape, and using Grasshopper it is difficult to customize some elements.

My next idea was to be able to transfer this model to Revit. After a quick tour on Google, I found the grasshopper plugin Hummingbird. Using Hummingbird plugin for Grasshopper you export geometry from Grasshopper to an Excel Spreadsheet and using Hummingbird plugin for Revit you import that Excel Spreadsheet.

BrideFinal

At the end the process became quite complicated. When changing in input value in Grasshopper it took more 30 minutes to populate that changes to Revit.

I finally thought about using Dynamo to create directly a parametric model of the bridge within Revit. Unfortunately Dynamo is quite hard to use and I think it would make a great subject for a future post on BIM 42.

Thank you so much for this content, and I must say I totally agree with you about Dynamo, I should already have started to work with it.

BFC Reader

I was talking on my previous post about creating a report from a Open BIM Collaboration Format. This format can be exported from Tekla BIMSight.

I am using the Open BIM Collaboration Format on a daily basis for taking notes during coordination meetings. I am using Tekla BIMSight to create these notes, but any model review solution could do the trick, as long as you can export BCF files from it.

These notes are quite useful for addressing coordination problems, but cannot be seen outside a model.

After the proof of concept I presented to you on my last post, I finally took the time to build a packaged application in order to create a Microsoft Word document from a BCF report.

BCF Reader

Aside from minor technical problems, I was most concerned by the possibilities to edit the style of the report before creating it, and avoid the tedious task to clean it up in Word after.

I finally selected a solution mixing Word template and styles. All you have to do after selecting you BCF report is to load a Word template. The application will automatically retrieve all styles in it, and you will be able to select them for each part of your report.
These parts are described in the picture below, where every information embedded in the BCF note is written down on the report.

Report

You can then save your report in a new word document.

The application can be downloaded here, under the MIT licence.

The entire source code is also available on Bitbucket, feel free to use it for your own project.

Creating a report from a BCF file

I was talking on a previous post about BIMsight and the possibility to export its notes in the BCF format.

This BIM Collaboration Format (BCF) is a common development by Tekla and Solibri to create a standard for exchanging comments between building models. This format can accelerate dataflow during project review by exchanging only comments without having to rely upon the same format and large data exchange through Internet.

The BCF format is currently supported by Tekla Structures, Tekla BIMsight, ArchiCAD, Kubus BCF Manager, Solibri, Elvis, Kymdata’s CADS Planner softwares, DDS-CAD Viewer and DDS-CAD MEP. There is also plug-ins for Revit and Navisworks.

I am using Tekla BIMSight on a daily basis as an advanced BIM notebook. Every problem is addressed during the daily coordination meeting, and documented using notes in Tekla BIMSight.

Example

But for documentation purpose, I also need a paper-based report, quite old fashioned, but handy when you have to work with people without Tekla BIMSight.

I created a little standalone program for converting BCF files to Word reports. These BCF files are created from my notes in Tekla BIMSight.

A BCF file is actually a compressed file, where every note is stored in its own folder, named with the note GUID:

Folder

In each of these folder, there is three files:

markup.bcf
snapshot.png
viewpoint.bcfv

The markup.bcf file stores all metadata about the note: Its date, its title, its author, its various comment along with their dates, and so on. This is the main source of information for my daily coordination report.

snapshot.png is the first image associated with the note, and an essential part of my report too.

Finally, the viewpoint.bcfv store information about the position of the camera used to capture the snapshot. Since the very point of my report is to work outside the model, I won’t use it here.

I use the XSD Schema provided by Building Smart to create my C# classes and serialize the markup.bcf file.

To write down this report, I use the great DocX library to create a Word 2010 file.

With this little program, I create automatically a nice Word report from my coordination notes, and can share my comments with everyone who does not have Tekla BIMSight.

Presentation1

Getting started programming Revit – Part 2

Last week, we started creating macros in Revit, we saw how to get the current Revit document, and retrieved every visible walls in the current view.

We will see today how to create a tag on these walls.

To tag an element in Revit, we need the Create.NewTag() function. This function is called from the current document, named here myDocument. But to work properly, this function needs a few things as inputs.

It first requires a view to place our tag: we will just use the active view, named myActiveView.

It also needs an element to place a tag on. To do so, we will select each one of our walls with a for each loop.

foreach (Element myElement in myWalls) {
    //Do something with myElement
}

It means that for every element contained in our list of wall myWalls, we will perform some action, written between the brackets.

A few options have to be set, like the category of our tag, its orientation and its leader.

Finally, it needs a location point to insert our tag. We want our tag to be placed at the center of our wall, so we will retrieve the baseline of our wall, and create a point in the middle of this baseline:

//Get our wall
Wall myWall = myElement as Wall;
//Get its location
LocationCurve myWallLocation = myWall.Location as LocationCurve;
//Get starting point
XYZ myWallStartingPoint = myWallLocation.Curve.GetEndPoint(0);
//Get ending point
XYZ myWallEndingPoint = myWallLocation.Curve.GetEndPoint(1);
//Create the middle point
XYZ myWallCenterPoint =(myWallStartingPoint + myWallEndingPoint)/2;

Know, we have everything we need to create our tag:

  IndependentTag myTag = myDocument.Create.NewTag(
                myActiveView,
                myElement,
                false,
                TagMode.TM_ADDBY_CATEGORY,
                TagOrientation.Horizontal,
                myWallCenterPoint);

To try this, we need to draw some walls, and load in our model a Wall. We hit F8 to build the macro before running it.

But if we run it, we get the following error:

Error

Its means that we are trying to modifying something inside our model without starting what is called a transaction.

Every modification of our model has to be done within a transaction, a group of modifications that can be discarded. If you remember the list of actions we can cancel in the Revit user interface, each one of them is a transaction that had to be started be before modifying anything in our model.

Transactions

So let create a transaction:

We start by defining a scope of our transaction with the keyword using. Every piece of code between the following brackets will use the transaction named tx.

using (Transaction tx = new Transaction(myDocument))
{

}

Now our transaction is created, we can start it, execute our code, and commit these modifications in our transaction:

using (Transaction tx = new Transaction(myDocument))
{
                tx.Start("Add Tags on walls");

                foreach (Element myElement in myWalls) {
                    //Do something with myElement

                    //Get our wall
                    Wall myWall = myElement as Wall;
                    //Get its location
                    LocationCurve myWallLocation =
                             myWall.Location as LocationCurve;
                    //Get starting point
                    XYZ myWallStartingPoint =
                             myWallLocation.Curve.GetEndPoint(0);
                    //Get ending point
                    XYZ myWallEndingPoint =
                             myWallLocation.Curve.GetEndPoint(1);
                    //Create the middle point
                    XYZ myWallCenterPoint =
                        (myWallStartingPoint + myWallEndingPoint)/2;

                    IndependentTag myTag = myDocument.Create.NewTag(
                             myActiveView,
                             myElement,
                             false,
                             TagMode.TM_ADDBY_CATEGORY,
                             TagOrientation.Horizontal,
                             myWallCenterPoint);
                }

                tx.Commit();
}

We run it and every walls are tagged.

Walls

Getting started programming Revit

I recently had some questions on how to start programing in Revit, so I will present here a simple macro created with the embedded macro editor, SharpDevelop.

As an example, I will create a macro for tagging all wall in a given view. Even if this function already exist through the Tag All command, you will see all the possibilities for creating it with a macro.

This week, we will see how to create a macro and retrieve elements draw in our model.

Let’s start it by clicking on the Macro Manager on the Manage tab:

Macro Manager

This is where you create new function in Revit, called “Macro”. These macro are lines of code, conveniently stored in a “Module”.

Macro Manager Interface

After creating a new module, and creating a new macro in this module, Revit starts its embedded code editor, called SharpDevelop. This is where we are going to spend most of our time developing Revit macro.

SharpDevelop

We can see the name of our macro just after “public void” and before a pair of brackets. We will write all our code between these brackets.

Every action done within Revit are made within a document, generally a .rvt or .rfa file. We have to explain this in our macro by retrieving the current document

Let’s write this:

Document myDocument;

With this line, I explain to Revit that something, a variable, called “myDocument” is a Revit Document.

Don’t forget the trailing “;”, it means the end of the line and has to be placed after every instruction line in a Revit macro.

Then, we write this:

myDocument = this.ActiveUIDocument.Document;

With this line, we explain than myDocument is actually the active document, the Revit document I am currently working with. “this” mean here “this Revit application”.

To be able to annotate every walls in a view, we have to select this view. Here, we call our view myActiveView and set it to the currently active view, the one you are looking at right now.

View myActiveView = myDocument.ActiveView;

Now than our Revit document and the view are selected, we have to retrieve walls to be able to annotate them. The Revit API provide us with a great function, the ability to filter element by category, type, class, … well, pretty much everything.

First, let’s create our filter, with the keyword “new”:

FilteredElementCollector filter 
= new FilteredElementCollector(myDocument,myActiveView.Id);

This “filter” will search for every element contained in “myDocument” and visible in the view “myActiveView”. The “.Id” after “myActiveView” mean that we use the unique identifier of the view instead of the view itself to create our filter.

We can now use this filter to actually catch some walls. Let’s write that:

List<Element>myWalls 
= filter.OfCategory(BuiltInCategory.OST_Walls).ToList();

We create a list of Revit element named myWalls and retrieve every element of the category Wall (“BuiltInCategory.OST_Walls”). The trailing “.ToList()” convert our filter into an actual list of elements.

Before going any further, we try this. Back in Revit, we draw four walls and run our macro by selecting it in the Macro Manager and hitting “Step Into”. This sends us back to SharpDevelop, with slight changes in the interface, and a yellow highlight on the first line of our macro. This highlight show us which line of our macro is currently executed. Hit “F11” two times to pass the first line.

If we pass our cursor on “myDocument”, a hint appear, showing us than “myDocument” is actually a document.

Document Hint

Let’s hit “F11” a few time to pass the last line. Stop right after it. If we pass our cursor on “myWalls” and click on the small “+” in the highlight, we see the list of walls retrieved by our filter. Everything works as expected, so far.

Walls Hint

We hit “F5” to run in a single stroke the remaining line of code and go back into SharpDevelop.

Next week, we will see how to use this list of walls to create a tag for every one of them.