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

Wall openings

I came back from the first meeting of the Paris Revit User Group. Julien Benoit give us great insights on how to use Dynamo for data management and Revit automation.

His point of view give me some ideas to feed my current obsession, wall openings.

Modeling opening where ducts, pipes, cable trays or conduits intersect walls or floors can be a tedious business. Anyway, it always relies on the same underlining principle: we place a face-base opening family on the wall at the intersection with the duct, the pipe or the cable tray. We also respect a few rules of thumb when placing these openings to keep a structurally sound wall.

Concrete-Formwork

There is a handful of plug-in for placing them automatically but they all need a fair amount of work to replace them properly afterward, so I decide to stick to a more “manual” solution.

First of all, I use Navisworks to find where I have to place these openings. It gives me a list of walls. Then, I use Dynamo to create an elevation in front of each of these walls. These elevations ease the process for placing the opening family in the concrete model.

The entire process can be sum up like this:

processComplete

The Dynamo definition use the wall bounding box and normal to create the section view coordinate system. I fumble around with Min and Max points to set the proper crop box for the final view. I also use a few nodes from archi-lab.net package to retrieve walls from their ids. You can find the entire Dynamo definition here.

“A problem well defined is a problem half solved”, and displaying a view of each problem is my first step toward the solution, even if I haven’t find yet any way to automate the entire wall opening thing.

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

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

}

Automatic Modeling

Most questions I encounter these days turn around using building information modeling for producing construction drawings, schedules or clash detection reports. These features are important, of course, but they remain centered around using a more or less complete building model. But a largely overlooked use of building information model is the ability to automatically model building elements. In fact, object-oriented modeling should allow us to simply describe our solutions and let the computer implement them in our building model, and even warn us when something doesn’t fit.

Lead by this thought, and a problem raised by one of my colleague, I started looking again toward slab insulations. Before BIM, slab insulation was simply annotated on the drawing, and taken into account when creating a building section. Now, for coordination purpose, we have to spend large amount of time modeling everywhere a fairly simple element like insulation, without real added value.

There is nothing new with this problem, and I was already trying to find a solution for modeling insulation layers on the upper face of a series of spaces with my latest version of RoomFinishes, without finding a decent solution.

But while looking for something totally different, I came across this article in AUGI about room and spaces in Revit. So after five years working with Revit, I realized than by checking this small checkbox in the “Area and Volume Computations”, the room continues up until it fit a room bounding element, a ceiling or a floor.

VolumeComputation

Energized by this nice discovery, I created a small application for automatically modeling insulation. This application retrieves every faces of a given room, and uses the higher ones as references for modeling floors and walls as insulations.

Insulation

The only problem here is framing. They are not room bounding elements, and the upper face of the room does not fit the shape of the beam. I solved temporary this problem by creating a small floor under the beam.

beam

While doing this, I realized there is a lot of tricky cases, where insulation have to be modeled as wall or slab regarding of the configuration.

The entire code for this application can be found here. It is quite messy, but I hope it can help you anyway.

This small example illustrates one of the possibilities of working with building information models, where information can actually help us focus more on the actual design of the building and less on the representation of this design in the model.

Insulation Tag

I am not very fond of duct and pipe insulations in Revit. If the concept is very useful, I find difficult to add and edit them. Furthermore, seeing the insulation on every duct and pipe can quickly clutters the view. We want to know how much insulation we have, but we don’t want to see it everywhere.

In a technical drawing, we generally represent insulation only on small parts of the duct, with an annotation, and then infer that the duck is covered everywhere else.

To adapt this method on Revit, I create two detail components, representing the insulation on a round and on a rectangular duct.

Annotations

This components have a “Width” instance parameter for setting their width regarding the thickness of the insulation.

Width

I also create a small piece of code to instantiate these components on selected ducts and pipes.

The idea is to pick a series of duct and pipe, read their insulation thickness, and add a detail component with the corresponding thickness. Like this, we can control where we want to see an insulation, and improve the overall legibility of the drawing.

Animation

This solution allow me to create quickly some annotations to represent the insulation without having to display it everywhere. However, this is more a workaround than a real solution. Nothing here is adaptive, and you have to restart the tool each time you edit or even move your duct.

public void InsulationTag()
		{
			UIDocument uidoc = this.ActiveUIDocument;
			Document doc = uidoc.Document;
			
			Reference r = null;
			
			//Retrive the two symbols
			FamilySymbol rectangularSymbol = null;
			FamilySymbol roundSymbol = null;
			ICollection<Element> collection = new FilteredElementCollector(doc).OfClass(typeof(FamilySymbol)).OfCategory(BuiltInCategory.OST_DetailComponents).ToElements();
			foreach (Element element in collection)
			{
				FamilySymbol current = element as FamilySymbol;
				// This NewFamilyInstance overload requires a curve based family
				if (current.Family.Name == "DuctInsulationAnnotationRectangular")
				{
					rectangularSymbol = current;
				}
				else if (current.Family.Name == "DuctInsulationAnnotationRound")
				{
					roundSymbol = current;
				}
			}
			

			
			try
			{
				while (true) {
					
					using( Transaction t = new Transaction( doc ) )
					{
						t.Start( "Annotate Insulations" );

						r = uidoc.Selection.PickObject(ObjectType.Element, new SelectionFilter(),"Pick a duct");
						
						if (r != null)
						{
							MEPCurve mepCurve = doc.GetElement(r.ElementId) as MEPCurve;
							//doc.ActiveView.GetElementOverrides
							//Get the middle of the duct
							LocationCurve locCurve = mepCurve.Location as LocationCurve;
							XYZ insertionPoint = locCurve.Curve.Evaluate(0.5,true);
							//Get the angle
							XYZ vector = locCurve.Curve.GetEndPoint(1) - locCurve.Curve.GetEndPoint(0);
							double angle = new XYZ(1,0,0).AngleTo(vector);
							
							//Get the insulation
							ICollection<ElementId> insulationIds = InsulationLiningBase.GetInsulationIds(doc,r.ElementId);
							
							if (insulationIds.Count !=0)
							{
								InsulationLiningBase insulation = doc.GetElement(insulationIds.First()) as InsulationLiningBase;
								
								if (insulation != null)
								{
									FamilySymbol symbol = null;
									double widht;
									try {
										widht = insulation.Width;
										//Select the detail familly
										symbol = rectangularSymbol;
									} catch (Autodesk.Revit.Exceptions.InvalidOperationException) {
										Parameter outerDiameter = mepCurve.get_Parameter(BuiltInParameter.RBS_PIPE_OUTER_DIAMETER);
										if (outerDiameter != null)
										{
											widht = insulation.Thickness*2 + outerDiameter.AsDouble();
										}
										else
										{
											widht = insulation.Diameter;
										}
										
										//Select the detail familly
										symbol = roundSymbol;
									}
									
									//Create the annotation
									FamilyInstance annotation = doc.Create.NewFamilyInstance(insertionPoint,symbol,doc.ActiveView);
									
									//Change the witdh
									annotation.GetParameters("Width").First().Set(widht);
									
									//rotate the component
									annotation.Location.Rotate(Line.CreateUnbound(insertionPoint,new XYZ(0,0,1)),-angle);
								}
							}
						}

						t.Commit();
					}
				}
				
			}
			catch( Autodesk.Revit.Exceptions.OperationCanceledException )
			{
			}
		}

public class SelectionFilter : ISelectionFilter
	{
		#region ISelectionFilter Members

		public bool AllowElement(Element elem)
		{

			if (elem.Category.Id.IntegerValue == (int)BuiltInCategory.OST_DuctCurves) return true;
			if (elem.Category.Id.IntegerValue == (int)BuiltInCategory.OST_PipeCurves) return true;

			return false;
		}

		public bool AllowReference(Reference refer, XYZ pos)
		{
			if (refer.ElementReferenceType == ElementReferenceType.REFERENCE_TYPE_NONE) return false;

			if (refer.ElementReferenceType == ElementReferenceType.REFERENCE_TYPE_SURFACE) return true;
			if (refer.ElementReferenceType == ElementReferenceType.REFERENCE_TYPE_LINEAR) return true;

			return false;
		}

		#endregion
	}

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

LazJS for Revit

I recently receive a beta version of LazJS, a Javascript editor embedded within Revit.

Even if I more fluent with .NET and C#, I find the initiative quite interesting, especially when you have to quickly develop some small routine within Revit.

After installing it, I click on the ParamJS button to start their embedded editor.

LazJS

As an example, I concatenate various parameters of a sheet in the sheet name in order to have them when I print or export it.

I start by selecting a category in the Categories list. Here, you can select multiple categories, but I will only select “Sheets” here. When I click on “Customize”, LazJS displays every editable parameters of our sheets in the “Parameters” list. Since I want to edit the name of my sheet, I select “Sheet Name” and save.

ParamJS

I can now drag and drop parameters from the “Formulas” list to the “Editor” panel. This creates automatically the correct line of code for calling this parameter. I drag some of my sheet parameters, add some “+” to concatenate them, and hit Run. We can see the result of my concatenation just below. I hit save, and my modification are added to my model.

Run

If this is the quickest way to edit parameters, LazJS have also a fully functional editor, which allows us to create more elaborate routines.

I use it to create the same concatenation tool than in my previous example, everything came pretty easily with the auto-completion.

ScreenClip [1]

LazJS also provide a set of already existing scripts for inspiration.

Last but not least, LazJS provides functions for exporting and importing data to Excel. These functions are very easy to use, and can replace any plug-in dedicated to exporting and importing data from Excel.

Even if I will keep my habit with C#, the LazJS plug-in seems quite powerful, and far more easy to use than the Revit macro editor, since you don’t have to handle all the tedious document retrieval and the transaction handling.

The only problem I have to report is an error when I attempt to debug a Revit macro in SharpDevelop after installing LazJS. It is kind of annoying, but I am sure this problem will be addressed when they launch their final version.

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.