- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
UPDATE: 4/28/2025 - After some further debugging it appears that if I change the face selection to manual and use the face reference overload it works, which makes it seem that it is something in the automatic face detection that is not working. Anyone have any ideas what is wrong in that block of code?
Hello all,
I am trying to insert a face based family onto the top face of an element using NewFamilyInstance Method (Face, XYZ, XYZ, FamilySymbol). From what I can tell, it is getting the correct face and inserting the family, however it is inserting it at a z=0 elevation rather than hosting to directly to to the face of the host. I need it to work exactly like manually hosting to the face so that once I move, rotate, or twist the host, the hosted family will follow with it.
I am trying to follow @jeremy_tammik 's post "Insert Face Hosted Sprinkler" but it still continues to do the same thing. I had to make a few modifications to his GetPlanarFace method, as it was failing to get any solid objects from the geo(first "if(null != solid)" statement), but the instance ("else if" statement) seems to be finding the correct plane (seems to have the correct normal and elevation from the debug output).
The constraints are also quite different between the manually placed versus the API placed family instance.
If I do a snoop selection on each of both the manually placed and the API placed instance, you can see the "Host" element has a completely different ID as well as when you click on the host it shows one as a FamilySymbol and the other as a FamilyInstance
Does anyone know what I could be doing wrong here that is causing this unexpected behavior? Apologies in advance if this is an easy fix, I am very new to all of this. Thank you all in advance!
public Result InsertConnections(FINCast.UI.InsertPrecastElementsDialog.InsertResult insertResult)
{
try
{
Element hostElement = insertResult.HostElement;
string selectedType = insertResult.SelectedElementType;
string selectedRule = insertResult.SelectedRule;
// Load XML configuration
ACRule rule = LoadRuleConfig(selectedType, selectedRule);
// Get the connection family symbol
FamilySymbol symbol = GetFamilySymbol(rule);
PlanarFace topFace = GetPlanarFace(hostElement);
XYZ p = PointOnFace(topFace);
// Place instances
using (Transaction tx = new Transaction(doc, "Insert Connections"))
{
tx.Start();
FamilyInstance fi = doc.Create.NewFamilyInstance(topFace, p, XYZ.BasisX, symbol);
tx.Commit();
}
TaskDialog.Show("Success", $"Inserted {placementPoints.Count} Vector Connectors into Double Tee ID: {hostElement.Id.IntegerValue}.");
return Result.Succeeded;
}
catch (Exception ex)
{
TaskDialog.Show("Error", $"Failed to insert connections: {ex.Message}");
return Result.Failed;
}
}
XYZ PointOnFace(PlanarFace face)
{
XYZ p = new XYZ(0, 0, 0);
Mesh mesh = face.Triangulate();
for (int i = 0; i < mesh.NumTriangles; ++i)
{
MeshTriangle triangle = mesh.get_Triangle(i);
p += triangle.get_Vertex(0);
p += triangle.get_Vertex(1);
p += triangle.get_Vertex(2);
p *= 0.3333333333333333;
break;
}
return p;
}
private PlanarFace GetPlanarFace(Element element)
{
Options opt = new Options();
opt.ComputeReferences = true;
opt.DetailLevel = ViewDetailLevel.Fine; // Align with previous settings for consistency
GeometryElement geo = element.get_Geometry(opt);
PlanarFace topFace = null;
MessageBox.Show("Number of geometry objects: " + geo.Cast<GeometryObject>().Count());
foreach (GeometryObject obj in geo)
{
Solid solid = obj as Solid;
if (null != solid)
{
double volume = solid.Volume; // Volume in cubic feet
MessageBox.Show($"Solid found with volume: {volume} ft³");
// Skip solids with zero volume
if (volume <= 0.0)
{
MessageBox.Show("Skipping solid with zero volume.");
continue;
}
foreach (Face face in solid.Faces)
{
PlanarFace pf = face as PlanarFace;
if (null != pf)
{
XYZ normal = pf.FaceNormal;
XYZ centroid = pf.Evaluate(new UV(0.5, 0.5));
MessageBox.Show($"Planar face found: Normal = ({normal.X}, {normal.Y}, {normal.Z}), Centroid Z = {centroid.Z}");
if (normal.Z > 0.5)
{
topFace = pf;
break;
}
}
}
}
else if (obj is GeometryInstance instance)
{
GeometryElement instanceGeo = instance.GetInstanceGeometry();
if (instanceGeo != null)
{
foreach (GeometryObject nestedObj in instanceGeo)
{
Solid nestedSolid = nestedObj as Solid;
if (null != nestedSolid)
{
double volume = nestedSolid.Volume;
MessageBox.Show($"Nested solid found with volume: {volume} ft³");
if (volume <= 0.0)
{
MessageBox.Show("Skipping nested solid with zero volume.");
continue;
}
foreach (Face nestedFace in nestedSolid.Faces)
{
PlanarFace pf = nestedFace as PlanarFace;
if (null != pf)
{
XYZ normal = pf.FaceNormal;
XYZ centroid = pf.Evaluate(new UV(0.5, 0.5));
MessageBox.Show($"Nested planar face found: Normal = ({normal.X}, {normal.Y}, {normal.Z}), Centroid Z = {centroid.Z}");
if (normal.Z > 0.5)
{
topFace = pf;
break;
}
}
}
}
else
MessageBox.Show("No solid found in nested geometry object.");
}
}
else
MessageBox.Show("No nested geometry found in GeometryInstance.");
}
else
MessageBox.Show("No solid found in geometry object.");
if (topFace != null)
break;
}
if (null != topFace)
{
MessageBox.Show($"Selected top face: Normal = ({topFace.FaceNormal.X}, {topFace.FaceNormal.Y}, {topFace.FaceNormal.Z})");
return topFace;
}
else
throw new InvalidOperationException("No suitable planar face found.");
}
Solved! Go to Solution.