Finding those constraining dimensions

Steve asks a lot of good questions and it is nice that the API is often able to help answer them. Recently he asked “Where are the Constraining Dimensions?

Here’s how to use the Dimension.IsLocked property to find those constraining dimensions:

Capture

For the entries where a segment of a multi-segment dimension is locked (such as #s 30, 31, 32), every reference of every segment is listed instead of just listing the two references related to the locked segment. This happens because while the Dimension class does has a References property, the DimensionSegement class has no such property. So the output is all references of the “parent” dimension instead of the two references of the relevant segment.

Capture

public void getConstraints()
{
    Document doc = this.ActiveUIDocument.Document;
    string data = "";
    int ctr = 1;
    foreach (Dimension d in new FilteredElementCollector(doc)
             .OfClass(typeof(Dimension))
             .Cast<Dimension>())
    {                
        if (d.NumberOfSegments == 0 && d.IsLocked)
        {
            data += ctr + ": id = " + d.Id.IntegerValue + " " + getReferenceNames(doc, d.References) + Environment.NewLine;
            ctr++;
        }
        else if (d.NumberOfSegments > 1)
        {
            foreach (DimensionSegment ds in d.Segments)
            {
                if (ds.IsLocked)
                {
                    data += ctr + ": id = " + d.Id.IntegerValue + " " + getReferenceNames(doc, d.References) + " - segment" + Environment.NewLine;
                    ctr++;
                }
            }
        }
    }
    if (data == "")
        data = "No locked dimensions";
    
    TaskDialog td = new TaskDialog("Locked Dimensions");
    td.MainContent = data;
    td.Show();
}

private string getReferenceNames(Document doc, ReferenceArray ra)
{
    string ret = "";
    foreach (Reference r in ra)
    {
        Element e = doc.GetElement(r);
        if (e.Category != null)
            ret += "(" + e.Category.Name + ") ";
        ret += e.Name + " & ";
    }
    
    // trim trailing " & "
    ret = ret.Remove(ret.Length - 3);
    
    return ret;
}

#AU2013 Wish granted! Convert multiple dimensions into one

Brok asked “Can you via #Revit API combine individual dims into a single string and remove the old dims?”

Yes!

The tricky part was figuring out how to avoid creating zero-length dimension segments between the references used by adjacent dimensions.

public void dimensionConsolidation()
{
    Document doc = this.ActiveUIDocument.Document;
    Application app = this.Application;
    UIDocument uidoc = new UIDocument(doc);

    IList<ElementId> idsToDelete = new List<ElementId>();
    ReferenceArray dimensionRefsForNewDimension = new ReferenceArray();

    // list of element ids and geometry objects used to weed out duplicate references
    IList<Tuple<ElementId,GeometryObject>> geomObjList = new List<Tuple<ElementId,GeometryObject>>();

    Line line = null;
    DimensionType dimType = null;
    IList<Element> elementList = uidoc.Selection.PickElementsByRectangle();    
    if (elementList.Count == 0)
        return;

    foreach (Element selectedElement in elementList)
    {
        Dimension d = selectedElement as Dimension;
        idsToDelete.Add(d.Id);

        // take the dimension line & dimension type from the first dimension
        if (line == null)
        {
            line = d.Curve as Line;
            dimType = d.DimensionType;
        }

        foreach (Reference dr in d.References)
        {
            Element thisElement = doc.GetElement(dr);
            GeometryObject thisGeomObj = thisElement.GetGeometryObjectFromReference(dr);

            // do not add references to the array if the array already contains a reference
            // to the same geometry element in the same element
            bool duplicate = false;
            foreach (Tuple<ElementId,GeometryObject> myTuple in geomObjList)
            {
                ElementId idInList = myTuple.Item1;
                GeometryObject geomObjInList = myTuple.Item2;
                if (thisElement.Id == idInList && thisGeomObj == geomObjInList)
                {
                    duplicate = true;
                    break;
                }
            }

            if (!duplicate)
            {
                dimensionRefsForNewDimension.Append(dr);
                geomObjList.Add(new Tuple<ElementId, GeometryObject>(thisElement.Id, thisGeomObj));
            }
        }
    }    

    using (Transaction t = new Transaction(doc, "Dimension Consolidation"))
    {
        t.Start();
        Dimension newDim = doc.Create.NewDimension(doc.ActiveView, line, dimensionRefsForNewDimension, dimType);
        doc.Delete(idsToDelete);
        t.Commit();
    }

}

Setting door position relative to nearby walls

A reader sent the image on the right and asked: “can we make door with specific distance to face of waDoorPositionll like 20 cm without every time need to adjust distance in temporary dimension ?”

This seemed like a good excuse to stop writing code dealing with Family Parameters (I was getting tired of writing it, were you getting tired of reading about it?).

public void DoorWallOffset()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;

    // establish an offset of 20cm that will be applied to the door and wall
    // Revit's internal units for distance is feet
    double doorToWallDistance = 20 / (12 * 2.54); // convert 20 cm to 1 ft

    try // try/catch block with PickObject is used to give the user a way to get out of the infinite loop of while (true)
        // PickObject throws an exception when user aborts from selection
    {
        while (true)
        {
            FamilyInstance door = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select a door. ESC when finished.").ElementId) as FamilyInstance;
            double doorWidth = door.Symbol.get_Parameter(BuiltInParameter.DOOR_WIDTH).AsDouble();

            Wall sideWall = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select a wall. ESC when finished.").ElementId) as Wall;
            double sideWallWidth = sideWall.Width;

            // get the curve that defines the centerline of the side wall
            LocationCurve sideWallLocationCurve = sideWall.Location as LocationCurve;
            Curve sideWallCurve = sideWallLocationCurve.Curve;

            // get the curve that defines the centerline of the wall that hosts the door
            Wall hostWall = door.Host as Wall;
            LocationCurve hostWallLocationCurve = hostWall.Location as LocationCurve;
            Curve hostWallCurve = hostWallLocationCurve.Curve;

            // find the point of intersection of these two wall curves (intersectionXYZ)
            // and the position of this point on the wall that hosts the door (intersectionParam)
            IntersectionResultArray ira = new IntersectionResultArray();
            SetComparisonResult scr = sideWallCurve.Intersect(hostWallCurve, out ira);
            var iter = ira.GetEnumerator();
            iter.MoveNext();
            IntersectionResult ir = iter.Current as IntersectionResult;
            XYZ intersectionXYZ = ir.XYZPoint;
            double intersectionParam = hostWallCurve.Project(intersectionXYZ).Parameter;

            // find the position of the door in its host wall
            LocationPoint doorPoint = door.Location as LocationPoint;
            XYZ doorXYZ = doorPoint.Point;
            double doorParam = hostWallCurve.Project(doorXYZ).Parameter;

            // compute the translation vector between the edge of the door closest to the wall and the side face of the wall
            XYZ translation = null;
            XYZ doorEdgeXYZ = null;
            XYZ intersectionOffsetXYZ = null;
            if (intersectionParam > doorParam)
            {
                intersectionOffsetXYZ = hostWallCurve.Evaluate(intersectionParam - doorToWallDistance - sideWallWidth/2, false);
                doorEdgeXYZ = hostWallCurve.Evaluate(doorParam + doorWidth/2, false);
                translation = intersectionOffsetXYZ.Subtract(doorEdgeXYZ);
            }
            else
            {
                intersectionOffsetXYZ = hostWallCurve.Evaluate(intersectionParam + doorToWallDistance + sideWallWidth/2, false);
                doorEdgeXYZ = hostWallCurve.Evaluate(doorParam - doorWidth/2, false);
                translation = doorEdgeXYZ.Subtract(intersectionOffsetXYZ).Negate();
            }

            // Move the door
            using (Transaction t = new Transaction(doc,"move door"))
            {
                t.Start();
                ElementTransformUtils.MoveElement(doc,door.Id,translation);
                t.Commit();
            }
        }
    }
    catch{}
}

Scrubbing Out Dimension Styles

selectAllInstance

Over at AUGI a good question was asked about how to get undesired dimension styles out of a project.

Dimensions in sketches and unplaced groups are at least part of the problem because right-clicking on a dimension and choosing “Select All Instances – In Entire Project” doesn’t select them.

Here is a macro that handles both dimensions in sketches and dimensions in unplaced groups. The dimensions in sketches are found as part of a normal filter to find all dimensions in the project. Dimensions in unplaced groups needed to be treated specially because an instance of the group needs to be created before the dimension in the group can be changed.

public void DimensionSytleScrub()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = new UIDocument(doc);

    // Prompt user to select a dimension with an unwanted style
    Dimension selected = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element), "Select dimension with style to scrub.") as Dimension;
    // Get name of the dimension style of this dimension
    string toScrub = selected.DimensionType.Name;

    // Find the dimension style named "Default" which will be used to replace the unwanted style
    // There must be a style named "Default" for this command to work
    DimensionType defaultType = (from v in new FilteredElementCollector(doc)
                                                        .OfClass(typeof(DimensionType))
                                                        .Cast<DimensionType>()
                                                        where v.Name == "Default" select v).First();

    using (Transaction t = new Transaction(doc,"Change dimension style to default"))
    {
        t.Start();
        // loop through all dimensions with the unwanted style
        foreach (Dimension dimension in (from v in new FilteredElementCollector(doc).OfClass(typeof(Dimension)).Cast<Dimension>()
                              where v.DimensionType.Name == toScrub select v))
        {
            // change the dimension's style to the default style
            dimension.DimensionType = defaultType;
        }
        t.Commit();
    }

    // Dimensions in unplaced groups will not be found by the code above.
    // So we need to place an instance of each of these groups, find any dimensions in the group, and change their style if needed
    foreach (GroupType groupType in new FilteredElementCollector(doc).OfClass(typeof(GroupType)))
    {
        // if there are already instances of the group then skip to the next group type
         if (groupType.Groups.Size > 0)
             continue;
         using (Transaction t = new Transaction(doc,"Change dimension style in unplaced groups"))
         {
            // use this flag to track if any dimensions have been changed
            bool flag = false;
            t.Start();

            // place an instance of the group
            Group newGroup = doc.Create.PlaceGroup(XYZ.Zero, groupType);

            // loop through all members in the group
            foreach (ElementId id in newGroup.GetMemberIds())
            {
                  // identify any dimensions in the group
                  Dimension dimension = doc.GetElement(id) as Dimension;
                  if (dimension != null)
                  {
                      // check if this dimension has the unwanted type
                      if (dimension.DimensionType.Name = toScrub)
                      {
                          // change the dimension type to the default type
                          dimension.DimensionType = defaultType;
                          flag = true;
                      }
                  }
            }
            doc.Delete(newGroup.Id); // delete the newly placed group
            if (flag)
                  t.Commit(); // commit this transaction if any dimensions were changed
            else
                  t.RollBack(); // no dimensions were changed, so throw away the entire transaction
         }
    }    
}