Placing face-based families on instance faces (GetInstanceGeometry or GetSymbolGeometry???)

You want to place a “marker” family instance where ducts meet columns.

If the marker is an un-hosted generic model (the sphere) everything is great. But if the marker is face-based (the hexagonal disk), why are they floating in space?

Capture1

The disks are in the wrong place because the code below uses GetInstanceGeometry, The Revit API Developers Guide tells us:

GetSymbolGeometry() returns the geometry represented in the coordinate system of the family. Use this, for example, when you want a picture of the “generic” table without regards to the orientation and placement location within the project. This is also the only overload which returns the actual Revit geometry objects to you, and not copies. This is important because operations which use this geometry as input to creating other elements (for example, dimensioning or placement of face-based families) require the reference to the original geometry.

GetInstanceGeometry() returns the geometry represented in the coordinate system of the project where the instance is placed. Use this, for example, when you want a picture of the specific geometry of the instance in the project (for example, ensuring that tables are placed parallel to the walls of the room). This always returns a copy of the element geometry, so while it would be suitable for implementation of an exporter or a geometric analysis tool, it would not be appropriate to use this for the creation of other Revit elements referencing this geometry.

public void DuctColumnIntersect()
{
	Document doc = this.ActiveUIDocument.Document;
	UIDocument uidoc = this.ActiveUIDocument;
	
	Options option = new Options();
	option.ComputeReferences = true; // needed to place a family instance on a face of the column
	
	using (Transaction t = new Transaction(doc, "Duct Column Intersection"))
	{
		t.Start();			
		foreach (FamilyInstance columnInstance in new FilteredElementCollector(doc)
		         .OfClass(typeof(FamilyInstance))
		         .OfCategory(BuiltInCategory.OST_Columns))
		{				
			BoundingBoxXYZ bboxColumn = columnInstance.get_BoundingBox(null);

			List ducts = new FilteredElementCollector(doc)
				.OfClass(typeof(Duct))
				.OfCategory(BuiltInCategory.OST_DuctCurves)
				.WherePasses(new BoundingBoxIntersectsFilter(new Outline(bboxColumn.Min, bboxColumn.Max), 0.01))
				.Cast().ToList();
			
			GeometryInstance geomInst = columnInstance.get_Geometry(option)
				.Cast()
				.FirstOrDefault();
			Solid columnSolid = geomInst.GetInstanceGeometry()
				.Cast()
				.Where(q => q is Solid)
				.Cast()
				.FirstOrDefault(q => q.Faces.Size > 0);
								
			foreach (Face f in columnSolid.Faces)
			{
				foreach (Duct duct in ducts)
				{
					Curve ductCurve = ((LocationCurve)duct.Location).Curve;							
					IntersectionResultArray ira = null;
					SetComparisonResult scr = f.Intersect(ductCurve, out ira);
					if (ira == null)
						continue;
					IntersectionResult ir = ira.Cast().FirstOrDefault();
					if (ir == null)
						continue;
					XYZ pt = ir.XYZPoint;
					
					makePoint(doc, pt);
					makeHostedInstance(doc, pt, f);
				}
			}
		}
		t.Commit();
	}
}

private void makeHostedInstance(Document doc, XYZ pt, Face face)
{
    FamilySymbol fs = new FilteredElementCollector(doc)
    	.OfClass(typeof(FamilySymbol))
    	.Cast()
    	.FirstOrDefault(q => q.Family.FamilyCategory.Id.IntegerValue == (int)BuiltInCategory.OST_GenericModel && q.Family.Name == "hex" && q.Name == "hex");
    if (fs == null)
    	return;
    if (!fs.IsActive)
    	fs.Activate();
    FamilyInstance fi = (FamilyInstance)doc.Create.NewFamilyInstance(face, pt, XYZ.BasisZ, fs);
}

private void makePoint(Document doc, XYZ pt)
{
    FamilySymbol fs = new FilteredElementCollector(doc)
    	.OfClass(typeof(FamilySymbol))
    	.Cast()
    	.FirstOrDefault(q => q.Family.FamilyCategory.Id.IntegerValue == (int)BuiltInCategory.OST_GenericModel && q.Family.Name == "DiagPoint" && q.Name == "DiagPoint");
    if (fs == null)
    	return;
    if (!fs.IsActive)
    	fs.Activate();
	FamilyInstance point = (FamilyInstance)doc.Create.NewFamilyInstance(pt, fs, Autodesk.Revit.DB.Structure.StructuralType.NonStructural);
}

Here is the modified code that we need to correctly place the face-based disks on the column faces.

  1. Use GetSymbolGeometry instead of GetInstanceGeometry when getting the Solid of the column. When we do this, the faces of the solid will be the faces of the symbol (as they must be to place family instances on those faces)
  2. Instead of using the duct’s location curve as-is (which will not intersect any faces of the symbol geometry), I create a new line from the endpoints of the duct’s curve after applying the inverse of the column’s transform to the endpoints. This new line will be in the symbol’s coordinate system so it can be used for the Face.Intersect test with the symbol faces.
  3. When we find the point where this transformed duct curve and symbol face intersect, we then need to apply the column instance’s transform to those intersection points. This moves the intersection point from the symbol’s coordinate system to the model’s coordinate system.
Solid columnSolid = geomInst.GetSymbolGeometry()
	.Cast()
	.Where(q => q is Solid)
	.Cast()
	.FirstOrDefault(q => q.Faces.Size > 0);

Transform transform = columnInstance.GetTransform();

foreach (Duct duct in ducts)
{
	Curve ductCurve = ((LocationCurve)duct.Location).Curve;
	Line lineTranformed = Line.CreateBound(transform.Inverse.OfPoint(ductCurve.GetEndPoint(0)), transform.Inverse.OfPoint(ductCurve.GetEndPoint(1)));
	
	foreach (Face f in columnSolid.Faces)
	{
		IntersectionResultArray ira = null;
		SetComparisonResult scr = f.Intersect(lineTranformed, out ira);
		if (ira == null)
			continue;
		IntersectionResult ir = ira.Cast().FirstOrDefault();
		if (ir == null)
			continue;
		XYZ pt = ir.XYZPoint;
		
		makePoint(doc, transform.OfPoint(pt));
		makeHostedInstance(doc, transform.OfPoint(pt), f);
	}
}

 

Hurray!

Capture3