- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
Hi all,
I am working on some code that will get the top face of a family I have loaded into the model to use as a host for NewFamilyInstance. I am trying to use both the Reference and Face overloads for it but just cannot seem to get it to work the way it should.
If I use the basic most simple way of getting the face, I am pulling it from the instance geometry, but then it hosts to the family symbol of the host family rather than to the family instance of the host family
private Face GetTopFace(Element element)
{
var options = new Options()
{
ComputeReferences = true,
IncludeNonVisibleObjects = true,
DetailLevel = ViewDetailLevel.Fine
};
var topFace = element.get_Geometry(options)
.OfType<GeometryInstance>()
.SelectMany(g => g.GetInstanceGeometry().OfType<Solid>())
.Where(s => s.Volume > 0)
.OrderByDescending(s => s.Volume)
.FirstOrDefault()
?.Faces
.OfType<PlanarFace>()
.OrderByDescending(f => f.Area)
.FirstOrDefault();
if (topFace != null)
{
TaskDialog.Show("Debug", "Found topFace with Id: " + topFace.Id);
return topFace;
}
System.Diagnostics.Debug.WriteLine("Error: No suitable top face found using GetTopFace.");
return null;
}
If I use a ReferenceIntersector to get the face reference and pass that through, it inserts the family the correct way hosted to the family instance face.
private Reference GetFaceReference(Element element)
{
View3D view3D = GetOrCreateView3D();
if (view3D == null)
{
System.Diagnostics.Debug.WriteLine("Error: Could not obtain a 3D view for ReferenceIntersector.");
return null;
}
// Use ReferenceIntersector with a list of ElementIds
List<ElementId> targetElementIds = new List<ElementId> { element.Id };
ReferenceIntersector intersector = new ReferenceIntersector(targetElementIds, FindReferenceTarget.Face, view3D);
BoundingBoxXYZ bbox = element.get_BoundingBox(null);
if (bbox == null)
{
System.Diagnostics.Debug.WriteLine("Error: Host element lacks a bounding box.");
return null;
}
XYZ startPoint = new XYZ((bbox.Min.X + bbox.Max.X) / 2.0, (bbox.Min.Y + bbox.Max.Y) / 2.0, bbox.Max.Z + 100);
XYZ direction = -XYZ.BasisZ;
IList<ReferenceWithContext> refs = intersector.Find(startPoint, direction);
Reference topFaceRef = null;
double maxZ = double.MinValue;
// Fallback: Track the face with the highest Z-coordinate regardless of normal
Reference highestFaceRef = null;
double highestZ = double.MinValue;
foreach (ReferenceWithContext rwc in refs)
{
Reference r = rwc.GetReference();
GeometryObject geomObj = element.GetGeometryObjectFromReference(r);
XYZ centroid = r.GlobalPoint;
bool isTopFace = false;
XYZ normal = null;
if (geomObj is PlanarFace pf)
{
normal = pf.FaceNormal;
isTopFace = normal.Z > 0.4; // Lowered threshold to account for slight deviations
System.Diagnostics.Debug.WriteLine($"ReferenceIntersector Face: Type = PlanarFace, Normal = ({normal.X}, {normal.Y}, {normal.Z}), Global Centroid Z = {centroid.Z}");
}
else if (geomObj is CylindricalFace cf)
{
// For a CylindricalFace, compute the normal at the center
normal = cf.ComputeNormal(new UV(0.5, 0.5));
isTopFace = normal.Z > 0.4; // Lowered threshold to account for slight deviations
System.Diagnostics.Debug.WriteLine($"ReferenceIntersector Face: Type = CylindricalFace, Normal at center = ({normal.X}, {normal.Y}, {normal.Z}), Global Centroid Z = {centroid.Z}");
}
else
{
System.Diagnostics.Debug.WriteLine($"ReferenceIntersector Face: Type = {geomObj?.GetType().Name ?? "Unknown"}, Global Centroid Z = {centroid.Z} (skipped, unsupported type)");
continue;
}
// Track the face with the highest Z-coordinate as a fallback
if (centroid.Z > highestZ)
{
highestZ = centroid.Z;
highestFaceRef = r;
}
if (isTopFace && centroid.Z > maxZ)
{
maxZ = centroid.Z;
topFaceRef = r;
}
}
// Fallback: If no face meets the normal criteria, use the face with the highest Z-coordinate
if (topFaceRef == null && highestFaceRef != null)
{
topFaceRef = highestFaceRef;
maxZ = highestZ;
System.Diagnostics.Debug.WriteLine("Fallback: No face met normal criteria; selected face with highest Z-coordinate.");
}
if (topFaceRef != null)
{
GeometryObject topFaceObj = element.GetGeometryObjectFromReference(topFaceRef);
if (topFaceObj is PlanarFace topPlanarFace)
{
System.Diagnostics.Debug.WriteLine($"Selected Top Face: Type = PlanarFace, Normal = ({topPlanarFace.FaceNormal.X}, {topPlanarFace.FaceNormal.Y}, {topPlanarFace.FaceNormal.Z}), Global Centroid Z = {maxZ}");
}
else if (topFaceObj is CylindricalFace topCylindricalFace)
{
XYZ normal = topCylindricalFace.ComputeNormal(new UV(0.5, 0.5));
System.Diagnostics.Debug.WriteLine($"Selected Top Face: Type = CylindricalFace, Normal at center = ({normal.X}, {normal.Y}, {normal.Z}), Global Centroid Z = {maxZ}");
}
// Retrieve the face geometry object from the reference
GeometryObject topFaceObj1 = doc.GetElement(topFaceRef.ElementId).GetGeometryObjectFromReference(topFaceRef);
return topFaceRef;
}
System.Diagnostics.Debug.WriteLine("Error: No suitable top face found using ReferenceIntersector.");
return null;
}
FamilyInstance instance = doc.Create.NewFamilyInstance(topFaceRef, point, lengthDirection, symbol);
FamilyInstance instance1 = doc.Create.NewFamilyInstance(topFace, point, lengthDirection, symbol);
I would much rather use the simpler code to easily get the face for the instance, but just cannot figure out why it wants to host to the family symbol rather than the family instance. Any ideas on what I would need to tweak here to get it to host correctly?
Solved! Go to Solution.