In response to a tweet asking to save all groups, I responded that there is no API for Save Group. That’s true, but all hope is not lost.
This video shows how the API can be used to generate a text file listing all model groups. Then this text file is used as input to a journal file that saves each group to its own file.
Scott sent a tweet about deleting all revisions from a project. The tricky part is that, just like how the UI grays out the Delete button when there is only one revision in the project, we can’t use the API to delete the last revision.
This code uses the Revit 2015 API because the Revision class is new to 2015.
publicvoid DeleteRevisions()
{
Document doc = this.ActiveUIDocument.Document;
// get list of all revisions in the document
ICollection<ElementId> elementIds = new FilteredElementCollector(doc)
.OfClass(typeof(Revision))
.ToElementIds();
// Remove the first revision from the list// Revit must have one revision in the document, so we can't delete them all
elementIds.Remove(elementIds.First());
using (Transaction t = new Transaction(doc,"Delete Revisions"))
{
t.Start();
doc.Delete(elementIds);
t.Commit();
}
}
Last night at the API Open House, we talked about the power and limitations of the Dimensions API.
This example shows how to create dimensions that reference two parallel faces. The video shows running the command in a locked 3D view and selecting the end faces of beams. It is also necessary to set the view’s workplane so that the direction of the dimension lies in this plane.
More automated creation of these dimensions could be achieved by programatically getting the faces from the beams so that the user need only select the beam instead of the 3 faces shown in this sample.
publicvoid CreateDimFromFaces()
{
Document doc = this.ActiveUIDocument.Document;
UIDocument uidoc = this.ActiveUIDocument;
Reference r1 = uidoc.Selection.PickObject(ObjectType.Face, "Pick face for dimension reference");
Reference r2 = uidoc.Selection.PickObject(ObjectType.Face, "Pick face for dimension reference");
Reference r3 = uidoc.Selection.PickObject(ObjectType.Face, "Pick face for view work plane");
ReferenceArray ra = new ReferenceArray();
ra.Append(r1);
ra.Append(r2);
Element e = doc.GetElement(r2);
Location l = e.Location;
LocationCurve lc = l as LocationCurve;
Curve curve = lc.Curve;
Line line = curve as Line;
using (Transaction t = new Transaction(doc,"dim"))
{
t.Start();
doc.ActiveView.SketchPlane = SketchPlane.Create(doc, r3);
Dimension dim = doc.Create.NewDimension(doc.ActiveView, line, ra);
t.Commit();
}
}
Matt and some others asked how to “Copy All Rooms/Spaces to all phases in project”. This example shows how we can specify a “source phase” and create new rooms for each of those rooms in a “target phase”.
publicvoid copyRoomPhase()
{
string sourcePhaseName = "New Construction";
string targetPhaseName = "Existing";
Document doc = this.ActiveUIDocument.Document;
Phase targetPhase = new FilteredElementCollector(doc)
.OfClass(typeof(Phase))
.Cast<Phase>()
.FirstOrDefault(q => q.Name == targetPhaseName);
Phase sourcePhase = new FilteredElementCollector(doc)
.OfClass(typeof(Phase))
.Cast<Phase>()
.FirstOrDefault(q => q.Name == sourcePhaseName);
IList<Element> roomList = new FilteredElementCollector(doc)
.OfClass(typeof(SpatialElement))
.OfCategory(BuiltInCategory.OST_Rooms).ToList();
PlanTopologySet planTopologies = null;
using (Transaction t = new Transaction(doc,"get topology"))
{
t.Start();
planTopologies = doc.PlanTopologies;
t.Commit();
}
foreach (Element sourceRoom in roomList)
{
XYZ pointInSourceRoom = ((LocationPoint)(sourceRoom.Location)).Point;
Parameter phaseParam = sourceRoom.get_Parameter("Phase");
if (doc.GetElement(phaseParam.AsElementId()).Name != sourcePhaseName)
continue;
Room newRoom = null;
using (Transaction t = new Transaction(doc,"New Room"))
{
t.Start();
newRoom = doc.Create.NewRoom(targetPhase);
t.Commit();
}
bool found = false;
foreach (PlanTopology pt in planTopologies)
{
if (found)
break;
foreach (PlanCircuit pc in pt.Circuits.Cast<PlanCircuit>())
{
using (Transaction t = new Transaction(doc,"Place Room"))
{
t.Start();
Room placedNewRoom = doc.Create.NewRoom(newRoom,pc);
doc.Regenerate();
if (placedNewRoom.IsPointInRoom(pointInSourceRoom))
{
t.Commit();
found = true;
break;
}
else
{
t.RollBack();
}
}
}
}
}
}
Steve wishes for an easy way to use the API to find pinned elements. Wish granted!
publicvoid findPinned()
{
UIDocument uidoc = this.ActiveUIDocument;
Document doc = this.ActiveUIDocument.Document;
// create new SelElementSet to store elements that will be selected
SelElementSet selSet = SelElementSet.Create();
// find all elements in the active view that are pinnedforeach (Element e innew FilteredElementCollector(doc, doc.ActiveView.Id).WhereElementIsNotElementType().Where(q => q.Pinned))
{
// add each of these elements to the set
selSet.Add(e);
}
// select all elements in the set
uidoc.Selection.Elements = selSet;
// refresh the view so that the newly selected elements are highlighted
uidoc.RefreshActiveView();
TaskDialog.Show("Elements Pinned","Elements pinned in view '" + doc.ActiveView.Name + "' = " + selSet.Size.ToString());
}
Michael McCune sent a wish via Twitter to “replace detail group instances with a detail component”.
That’s a good wish, and one that can almost be completely granted via the API as shown in this video.
The limitation is that the API provides no way to identify the rotation angle of a group instance. For any element in a subclass of the Instance class (such as a family instance, import instance, or Revit link instance) the API provides GetTransform() which contains the data needed to determine the instance’s rotation angle. The AssemblyInstance class also implements GetTransform().
Unfortunately, the Group class has no such method. As a result, while we could rotate the newly created detail components, there is no way to query the detail group instance to determine how much each detail component should be rotated.
publicvoid detailGroupToComponent()
{
Document doc = this.ActiveUIDocument.Document;
// create a dictionary to store the mapping between detail groups and detail component// the NewLine character is used to delimit the family name and family type name
Dictionary<string, string> groupComponentDict = new Dictionary<string, string>();
groupComponentDict.Add("DG1","DetailComponent1" + Environment.NewLine + "Type 1");
groupComponentDict.Add("filled regions","DetailComponent2" + Environment.NewLine + "Type 2");
using (Transaction t = new Transaction(doc,"Detail Groups -> Detail Components"))
{
t.Start();
// find all group instances in the document with the Detail Groups category
IEnumerable<ElementId> groups = new FilteredElementCollector(doc)
.OfClass(typeof(Group))
.OfCategory(BuiltInCategory.OST_IOSDetailGroups).ToElementIds();
foreach (ElementId id in groups)
{
Group g = doc.GetElement(id) as Group;
// get the XYZ location of the group instance
Location location = g.Location;
LocationPoint lp = location as LocationPoint;
XYZ xyz = lp.Point;
// get the view that owns the group instance
ElementId ownerviewId = g.OwnerViewId;
View ownerview = doc.GetElement(ownerviewId) as View;
// lookup the name of the family that will replace this group instancestring familyNameType = groupComponentDict[g.Name];
// split the family name / family type stringstring[] split = familyNameType.Split(newstring[] {Environment.NewLine}, StringSplitOptions.None);
string famName = split[0];
string typeName = split[1];
// find the FamilySymbol (which is the API name for the Family Type)
FamilySymbol fs = new FilteredElementCollector(doc)
.OfClass(typeof(FamilySymbol))
.Cast<FamilySymbol>()
.FirstOrDefault(q => q.Family.Name == famName && q.Name == typeName);
// delete the group instance
doc.Delete(g.Id);
// create an instance of the detail component
doc.Create.NewFamilyInstance(xyz, fs, ownerview);
}
t.Commit();
}
}
Like last year, during the upcoming few days of Revit Technology Conference North America, please tweet your Revit API questions and wishes to me at twitter.com/BoostYourBIM. I will do my best to reply to as many as possible with code, videos, and blog posts showing how we can use the API to make Revit better.
I was having some problems with Windows (like Windows Explorer freezing when I try to create a new folder). So I did a Clean Boot and used shexview.exe to disable the non-Microsoft entries.
These changes seem to have fixed the problems I was having with Chrome and Windows Explorer, but now I can’t run Revit. Revit starts, the startup page briefly appears, then the Revit session dissapears. Does anyone know what might be happening? I’d really appreciate your help!
In a few weeks I will be speaking at Revit Technology Conference in Chicago about how the Revit API can help you maintain order and prevent chaos in your Revit adventures.
Techniques such as “using Worksets as a security guard” can work, but when you have 100s or 1000s of person-months of effort invested in your 400MB RVT file, you might want a much more complete set of tools to monitor, audit, and protect your model.
It would be great to have your input to shape this course (and related posts on this blog). Please leave your comments on this post or contact me at https://boostyourbim.wordpress.com/contact/