During RTCNA Boost Your BIM collaborated with Kelly Cone and Matt Mason to create a tool to measure complexity of Revit families. It is named in honor of Aaron Maller who makes some of the most sophisticated families around.
It scores each family and its top-level nested families based on the # of family parameters and # of operators (‘>’, ‘<‘, ‘+’, ‘-‘, ‘*’, ‘/’) in parameter formulas.
Thinking that others might like to continue the development of this tool, Boost Your BIM has created a public repository for the code and looks forward to seeing the enhancements that others might implement.
Clearing out my RTC API Wishlist Inbox… (do you have more wishes? RTCEUR is coming in 3 months)
Nicklas asks
Is it possible to create a macro that resets keynote when a family is duplicated?
The code and video below shows how Dynamic Model Update can be used to clear the Keynote parameter’s value any time a new Element Type is added to the file. But the problem with this is that it does not distinguish the different ways that a new Element Type can be added.
You can duplicate a type (the scenario that Nicklas wants to capture) but it also triggers when you load a family (a scenario that probably should not clear the keynote values for the just loaded families). The solution to this might include subscribing to the DocumentChanged event and using the GetTransactionNames Method to differentiate between a Duplicate and Load Family.
publicvoid addTrigger()
{
Application app = this.Application;
MyUpdater udpater = new MyUpdater(app.ActiveAddInId);
UpdaterRegistry.RegisterUpdater(udpater, true);
UpdaterRegistry.AddTrigger(udpater.GetUpdaterId(),
new ElementClassFilter(typeof(ElementType)), Element.GetChangeTypeElementAddition());
}
publicclass MyUpdater : IUpdater
{
static AddInId m_appId;
static UpdaterId m_updaterId;
public MyUpdater(AddInId id)
{
m_appId = id;
m_updaterId = new UpdaterId(m_appId, new Guid("FB1BA6B2-4C06-42d4-97C1-D1B4EB593EFA"));
}
publicvoid Execute(UpdaterData data)
{
Document doc = data.GetDocument();
try
{
foreach (ElementId id in data.GetAddedElementIds())
{
ElementType etype = doc.GetElement(id) as ElementType;
if (etype == null)
continue;
Parameter p = etype.get_Parameter(BuiltInParameter.KEYNOTE_PARAM);
if (p == null)
continue;
p.Set("");
}
}
catch (Exception ex)
{
string s = ex.Message;
TaskDialog td = new TaskDialog("MyUpdater Exception");
td.MainInstruction = ex.Message;
td.MainContent = ex.StackTrace;
td.Show();
}
}
publicstring GetAdditionalInformation() { return""; }
public ChangePriority GetChangePriority() { return ChangePriority.FloorsRoofsStructuralWalls; }
public UpdaterId GetUpdaterId() { return m_updaterId; }
publicstring GetUpdaterName() { return"MyUpdater"; }
}
Creating slab edges programmatically for all edges of a slab. The UI does not allow you to pick all edges of a slab in some cases. For example, on a slab edge that is sloping and curving in plan. Is there a way to get around this in the API by hard setting the reference curve?
It turns out that the limitation on sloping/curving edges still exists when we use the API, but in any case, below is the API code to create slab edges on all edges of the floor’s top face.
publicvoid slabEdge()
{
Document doc = this.ActiveUIDocument.Document;
UIDocument uidoc = this.ActiveUIDocument;
Floor floor = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element)) as Floor;
Options options = new Options();
options.ComputeReferences = true;
Solid solid = null;
foreach (Autodesk.Revit.DB.GeometryObject geomObj in floor.get_Geometry(options))
{
Autodesk.Revit.DB.Solid s = geomObj as Autodesk.Revit.DB.Solid;
if (null != s)
{
if (s.Faces.Size > 0)
{
solid = s;
break;
}
}
}
Face topFace = null;
double bigZ = 0;
foreach (Face f in solid.Faces)
{
PlanarFace pf = f as PlanarFace;
if (pf == null)
continue;
if (pf.FaceNormal.Z > bigZ)
{
bigZ = pf.FaceNormal.Z;
topFace = f;
}
}
SlabEdgeType slabType = new FilteredElementCollector(doc).OfClass(typeof(SlabEdgeType)).Cast<SlabEdgeType>().FirstOrDefault();
int ctr = 0;
using (Transaction t = new Transaction(doc, "slab edges"))
{
t.Start();
foreach (EdgeArray ea in topFace.EdgeLoops)
{
foreach (Edge e in ea)
{
try
{
SlabEdge se = doc.Create.NewSlabEdge(slabType, e.Reference);
se.HorizontalFlip();
ctr++;
}
catch
{
}
}
}
t.Commit();
}
TaskDialog.Show("Info", ctr + " slab edges created");
}
Load Group in to project from external Group file. .rvt file.
This could be particularly useful if you have a folder full of Group RVTs and want to load them all, or if you want to create a dialog box where the user can select multiple groups to load.
Unfortunately, the API does not have a simple Document.LoadGroup() method, so we have to do a bit more work.
publicvoid LoadModelGroup()
{
string groupFile = @"C:\Users\harry_000\Documents\MyGroup.rvt";
Application app = this.Application;
Document doc = this.ActiveUIDocument.Document;
// find elements to copy// to find the model elements in the group file,// create a 3d view and then get all the elements in that view
Document docGroup = app.OpenDocumentFile(groupFile);
ICollection<ElementId> toCopy = null;
using (Transaction t = new Transaction(docGroup, "temp"))
{
t.Start();
View3D view = View3D.CreateIsometric(docGroup, new FilteredElementCollector(docGroup)
.OfClass(typeof(ViewFamilyType))
.Cast<ViewFamilyType>()
.FirstOrDefault(q => q.ViewFamily == ViewFamily.ThreeDimensional).Id);
toCopy = new FilteredElementCollector(docGroup, view.Id).ToElementIds();
t.RollBack();
}
using (Transaction t = new Transaction(doc, "Copy Group"))
{
t.Start();
// paste the elements from the group file
ICollection<ElementId> newIds = ElementTransformUtils.CopyElements(docGroup, toCopy, doc, Transform.Identity, new CopyPasteOptions());
// group the newly pasted elements
Group group = doc.Create.NewGroup(newIds);
// set the name of the group type
group.GroupType.Name = Path.GetFileNameWithoutExtension(groupFile);
// delete the instance of the group
doc.Delete(group.Id);
t.Commit();
}
docGroup.Close(false);
}
The previous post showed how to create lines in an elevation view to visualize the view range of a plan view. Now let’s see how to let the user move those lines in the elevation view and update the plan view’s range.
publicvoid setViewRangeByLine()
{
string planName = "Level 1 View Range Test";
Document doc = this.ActiveUIDocument.Document;
ViewPlan viewPlan = new FilteredElementCollector(doc)
.OfClass(typeof(ViewPlan))
.Cast<ViewPlan>()
.FirstOrDefault(q => q.Name == planName);
if (viewPlan == null)
return;
CurveElement bottomCurve = new FilteredElementCollector(doc, doc.ActiveView.Id)
.OfClass(typeof(CurveElement))
.Cast<CurveElement>().FirstOrDefault(q => q.LineStyle.Name == "Bottom Clip Plane");
if (bottomCurve == null)
return;
double bottomZ = bottomCurve.GeometryCurve.GetEndPoint(0).Z;
PlanViewRange range = viewPlan.GetViewRange();
using (Transaction t = new Transaction(doc, "Set view range"))
{
t.Start();
range.SetOffset(PlanViewPlane.BottomClipPlane, bottomZ);
viewPlan.SetViewRange(range);
t.Commit();
}
}
View Ranges in Section to show graphically like a space in section (with Interior checked on) . Allow me to drag the box up/down to level if desired otherwise keep “Level Above”.
Here is part one – creating lines in an elevation view to show the view range of a plan view.
publicvoid showViewRange()
{
string planName = "Level 1 View Range Test";
Document doc = this.ActiveUIDocument.Document;
ViewPlan viewPlan = new FilteredElementCollector(doc)
.OfClass(typeof(ViewPlan))
.Cast<ViewPlan>()
.FirstOrDefault(q => q.Name == planName);
if (viewPlan == null)
return;
PlanViewRange range = viewPlan.GetViewRange();
Level bottomLevel = doc.GetElement(range.GetLevelId(PlanViewPlane.BottomClipPlane)) as Level;
Level topLevel = doc.GetElement(range.GetLevelId(PlanViewPlane.TopClipPlane)) as Level;
Category bottomClipLineStyle = doc.Settings.Categories.Cast<Category>().FirstOrDefault(q => q.Id.IntegerValue == (int)BuiltInCategory.OST_Lines)
.SubCategories.Cast<Category>().FirstOrDefault(q => q.Name == "Bottom Clip Plane");
Category topClipLineStyle = doc.Settings.Categories.Cast<Category>().FirstOrDefault(q => q.Id.IntegerValue == (int)BuiltInCategory.OST_Lines)
.SubCategories.Cast<Category>().FirstOrDefault(q => q.Name == "Top Clip Plane");
using (Transaction t = new Transaction(doc, "Make View Range Line"))
{
t.Start();
if (bottomLevel != null)
{
double bottomOffset = range.GetOffset(PlanViewPlane.BottomClipPlane);
double z = bottomLevel.Elevation + bottomOffset;
DetailCurve bottomLine = doc.Create.NewDetailCurve(doc.ActiveView, Line.CreateBound(new XYZ(0,0,z), new XYZ(30,0,z)));
if (bottomClipLineStyle != null)
{
bottomLine.LineStyle = bottomClipLineStyle.GetGraphicsStyle(GraphicsStyleType.Projection);
}
}
if (topLevel != null)
{
double topOffset = range.GetOffset(PlanViewPlane.TopClipPlane);
double z = topLevel.Elevation + topOffset;
DetailCurve topLine = doc.Create.NewDetailCurve(doc.ActiveView, Line.CreateBound(new XYZ(0,0,z), new XYZ(30,0,z)));
if (topClipLineStyle != null)
{
topLine.LineStyle = topClipLineStyle.GetGraphicsStyle(GraphicsStyleType.Projection);
}
}
t.Commit();
}
}
In Activate View event, If active workset contains “XX”, show task dialog warning user they are about to draw on an incorrect workset. We lock out users from “XX Shared Levels and Grids”, “XX Scope Boxes”, and “XX Links”
publicvoid registerEvent()
{
Application app = this.Application;
UIApplication uiapp = new UIApplication(app);
uiapp.ViewActivated += new EventHandler<ViewActivatedEventArgs>(uiapp_ViewActivated);
}
publicvoid uiapp_ViewActivated(object sender, ViewActivatedEventArgs e)
{
Document doc = e.Document;
string activeWorksetName = GetActiveWorkset(doc).Name;
if (activeWorksetName.Contains("XX"))
TaskDialog.Show("Warning", "Do not create elements in 'XX' workset: " + activeWorksetName);
}
public Workset GetActiveWorkset(Document doc)
{
// Get the workset table from the document
WorksetTable worksetTable = doc.GetWorksetTable();
// Get the Id of the active workset
WorksetId activeId = worksetTable.GetActiveWorksetId();
// Find the workset with that Id
Workset workset = worksetTable.GetWorkset(activeId);
return workset;
}
It’s that time of year again. Another Revit Technology Conference is up and running (or maybe walking to avoid a Phoenix heat-stroke) and Boost Your BIM is here to grant your API wishes!
Here are some examples of wishes that have been granted in the past. Please send your new wishes for interesting and useful ways the API might be able to help you make Revit better. Wishes can be tweeted to @BoostYourBIM or left as comments to this post