Hi Jim,
Many thanks for the suggestions. We have tried different functions from the API to solve the problem. Here is our current logic flow to find the adjacent(touching) neighbors of an element:
1. For a particular model element, we apply BoundingBoxIntersectsFilter to find nearby elements. We apply additional constraints on this filter to limit only to nearby model elements. The purpose of this filter is to reduce the search space for the next searches.
// Select something to use as base bounding box.
Reference r = uiDoc.Selection.PickObject(ObjectType.Element);
Element element = doc.GetElement(r.ElementId);
BoundingBoxXYZ bb = element.get_BoundingBox(doc.ActiveView);
Outline outline = new Outline(bb.Min, bb.Max);
// Create a BoundingBoxIntersectsFilter to
// find everything intersecting the bounding
// box of the selected element.
BoundingBoxIntersectsFilter bbfilter= new BoundingBoxIntersectsFilter(outline);
// Use a view to construct the filter so we
// get only visible elements.
FilteredElementCollector collector= new FilteredElementCollector(doc, doc.ActiveView.Id);
ICollection<ElementId> idsExclude
= new List<ElementId>();
idsExclude.Add(element.Id);
Options opt = new Options();
collector
.Excluding(idsExclude)
.WherePasses(bbfilter)
.WhereElementIsNotElementType()
.WhereElementIsViewIndependent()
.Where<Element>(e
=> null != e.Category
&& !ElementFilterManager.SkipThisBic(e.Category.Id.IntegerValue)
&& e.Category.HasMaterialQuantities //is it true for all geometric elements?
&& null != e.get_Geometry(opt));
static int[] _bics_to_skip = new int[] {
( int ) BuiltInCategory.OST_PreviewLegendComponents,
( int ) BuiltInCategory.OST_IOSSketchGrid,
( int ) BuiltInCategory.OST_Cameras,
( int ) BuiltInCategory.OST_Rooms,
( int ) BuiltInCategory.OST_SiteProperty
};
public static bool SkipThisBic(int bic)
{
return Array.Exists<int>(
_bics_to_skip,
delegate (int i)
{
return i == bic;
});
}
2. Among these nearby elements, we want to filter the elements that touch or intersect with the selected element. There are couple of methods in the API to find intersection between solids or elements. This methods don't provide desirable results when two elements are only touching each other and not intersecting. We tried the following approaches:
i. We tried the method ExecuteBooleanOperation of BooleanOperationsUtils class to find intersection between two solids. For this we first identified the list of solids from each element. For every pair of solids, one from each list, we apply ExecuteBooleanOperation to find whether there is any intersection. It returns null when two elements are only adjacent to each other.
public static double ComputeIntersectionVolume(Solid solidA, Solid solidB)
{
//double volumeOfIntersection = 0.0;
double surfaceAreaOfIntersection = 0.0;
Solid intersection = BooleanOperationsUtils.ExecuteBooleanOperation(solidA, solidB, BooleanOperationsType.Intersect);
if (intersection != null)
surfaceAreaOfIntersection = intersection.SurfaceArea;
return surfaceAreaOfIntersection;
}
ii. The next approach we tried is intersection of faces. We extracted the visible faces from the elements into two lists, one for each element. For every pair of faces, one from each list, we applied the Face.Intersect method to identify intersecting faces. If two faces are intersecting, we can say that the elements they are originating from also intersects.
public static List<double> ComputeIntersectionOfElements(Document doc, Element elementA, Element elementB)
{
Options opt = new Options();
opt.ComputeReferences = true;
opt.IncludeNonVisibleObjects = false;
List<double> results = new List<double>();
double NoOfIntersectingFaces = 0.0;
double NoOfIntersectingFacesFromManualCalculation = 0.0;
GeometryElement elementAGeoElement = elementA.get_Geometry(opt);
GeometryElement elementBGeoElement = elementB.get_Geometry(opt);
CurveArray curvesA = new CurveArray();
List<Solid> solidsA = new List<Solid>();
AddCurvesAndSolids(elementAGeoElement, ref curvesA, ref solidsA);
CurveArray curvesB = new CurveArray();
List<Solid> solidsB = new List<Solid>();
AddCurvesAndSolids(elementBGeoElement, ref curvesB, ref solidsB);
List<Face> facesOfA = new List<Face>();
List<Face> facesOfB = new List<Face>();
FacesFromSolids(solidsA, facesOfA);
FacesFromSolids(solidsB, facesOfB);
foreach (Face fa in facesOfA)
{
foreach (Face fb in facesOfB)
{
FaceIntersectionFaceResult fir = fa.Intersect(fb);
if (fir == FaceIntersectionFaceResult.Intersecting
&& fa.Visibility == Visibility.Visible && fb.Visibility == Visibility.Visible
)
{
NoOfIntersectingFaces += 1;
}
if (DoFacesIntersect(fa, fb))
{
NoOfIntersectingFacesFromManualCalculation += 1;
}
}
}
results.Add(NoOfIntersectingFaces);
results.Add(NoOfIntersectingFacesFromManualCalculation);
return results;
}
private static void FacesFromSolids(List<Solid> solidsA, List<Face> facesOfA)
{
foreach (Solid A in solidsA)
{
foreach (Face fa in A.Faces)
{
ElementId mid = fa.MaterialElementId;
if (mid == null)
{
continue;
}
facesOfA.Add(fa);
}
}
}
private static void AddCurvesAndSolids(Autodesk.Revit.DB.GeometryElement geomElem,
ref Autodesk.Revit.DB.CurveArray curves,
ref System.Collections.Generic.List<Autodesk.Revit.DB.Solid> solids)
{
foreach (Autodesk.Revit.DB.GeometryObject geomObj in geomElem)
{
Autodesk.Revit.DB.Curve curve = geomObj as Autodesk.Revit.DB.Curve;
if (null != curve)
{
curves.Append(curve);
continue;
}
Autodesk.Revit.DB.Solid solid = geomObj as Autodesk.Revit.DB.Solid;
if (null != solid && solid.Volume != 0 && solid.Visibility == Visibility.Visible && !solid.Edges.IsEmpty)
{
solids.Add(solid);
continue;
}
//If this GeometryObject is Instance, call AddCurvesAndSolids
Autodesk.Revit.DB.GeometryInstance geomInst = geomObj as Autodesk.Revit.DB.GeometryInstance;
if (null != geomInst)
{
Autodesk.Revit.DB.GeometryElement transformedGeomElem
= geomInst.GetInstanceGeometry(geomInst.Transform);
AddCurvesAndSolids(transformedGeomElem, ref curves, ref solids);
}
}
}
But this method gives us unexpected results. It returns false positive when there is an element close to a Window element. For example, in the attached Revit model, the window (next to tag 46) and the "Furniture:Seating - Artemis" element are intersecting even though they are not adjacent(highlighted in the attached picture). If we flip the facing of the Window instance, they are not intersecting anymore. We are quite curious what could be the reason behind this.
We have tried with our own face intersection method instead of the Face.Intersect method from the API. They are not giving us false positive, but they return negative for adjacent elements.
static private bool DoFacesIntersect(Face faceA, Face faceB)
{
foreach (EdgeArray edges in faceB.EdgeLoops)
{
foreach (Edge edge in edges)
{
IList<XYZ> epts = edge.Tessellate();
foreach (XYZ ept in epts)
{
// project the point to face and then check if the distance of point
// to face is zero.
// Note: if the nearest point is outside of this face,the result will null,
IntersectionResult intResult = faceA.Project(ept);
if (intResult != null)
{
// The project distance should be zero if point is on face
if (Math.Abs(intResult.Distance) < Double.Epsilon)
{
return true;
}
}
}
}
}
return false;
}
iii. We have investigated ElementIntersectsFilter and ElementIntersectsSolidFilter, still no success for adjacent elements. It seems they return null when the elements are adjacent only and non intersecting. It seems all methods or filters for finding intersection do not identify when two elements are only adjacent each other.
iv. We saw a discussion about filter for touching beams in buildingcoder. Here a new sphere is created at the end of a beam. ElementIntersectsSolidFilter is used to find the elements touching at the end of the beam. We are yet to find a solution which can be applied to any model element. Is there any efficient way to apply this solution for a model element with many curved faces and irregular shape?
Filter for Touching Beams Using Solid Intersection
Greetings!
Sharif