diff --git a/Revit_Core_Engine/Query/Space.cs b/Revit_Core_Engine/Query/Space.cs index 0622a0ab7..3cf8b161f 100644 --- a/Revit_Core_Engine/Query/Space.cs +++ b/Revit_Core_Engine/Query/Space.cs @@ -23,6 +23,7 @@ using Autodesk.Revit.DB; using Autodesk.Revit.DB.Mechanical; using BH.oM.Base.Attributes; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -35,12 +36,14 @@ public static partial class Query /**** Public methods ****/ /***************************************************/ + [PreviousVersion("9.1", "BH.Revit.Engine.Core.Query.Space(Autodesk.Revit.DB.Element, System.Collections.Generic.IEnumerable, System.Boolean)")] [Description("Returns the Revit Space that contains the given element.")] [Input("element", "The Revit element for which to find the containing Space.")] [Input("spaces", "An optional collection of Revit Spaces to search. If not provided, all Spaces in the element's document will be used.")] [Input("useRoomCalculationPoint", "If true and the element is a FamilyInstance with a spatial element calculation point, that point will be used for containment checks.")] + [Input("findClosestIfNotContained", "If true, if no containing Space is found, the method will attempt to find the closest Space in the direction of the element's connectors or below it.")] [Output("space", "The Revit Space containing the element, or the element itself if it is a Space. Returns null if no containing Space is found.")] - public static Space Space(this Element element, IEnumerable spaces, bool useRoomCalculationPoint = false) + public static Space Space(this Element element, IEnumerable spaces, bool useRoomCalculationPoint = false, bool findClosestIfNotContained = false) { if (element == null) return null; @@ -49,41 +52,83 @@ public static Space Space(this Element element, IEnumerable spaces, bool if (element is Space space) return space; - // 2. If the element is a FamilyInstance, try the .Space property - if (element is FamilyInstance fi && fi.Space != null) + // 2a. Check physical location - space property of family without calculation point + FamilyInstance fi = element as FamilyInstance; + if (fi != null && !fi.HasSpatialElementCalculationPoint && fi.Space != null) return fi.Space; - // 3. Use location point and check which space contains it - if (spaces == null) - { - Document doc = element.Document; - spaces = new FilteredElementCollector(doc) - .OfClass(typeof(SpatialElement)) - .OfType() - .ToList(); - } - else - { - m_LinkTransforms = spaces.GroupBy(s => s.Document) - .Where(g => g.Key.IsLinked) - .ToDictionary(g => g.Key, g => g.Key.LinkInstance().GetTotalTransform()); - } - - XYZ locationPoint = element.LocationPoint(useRoomCalculationPoint); - if (locationPoint == null) + // 2b. Check physical location - location point of the element + XYZ locationPoint = element.LocationPoint(false); + if (locationPoint == null) return null; + // Transform location point if element is from linked document Transform elementTransform = element.Document.IsLinked ? element.Document.LinkInstance().GetTotalTransform() : Transform.Identity; if (!elementTransform.IsIdentity) locationPoint = elementTransform.OfPoint(locationPoint); + // Collect spaces and their transforms if from linked documents + if (spaces == null) + spaces = new FilteredElementCollector(element.Document).OfClass(typeof(SpatialElement)).OfType().ToList(); + else + m_LinkTransforms = spaces.GroupBy(s => s.Document).Where(g => g.Key.IsLinked).ToDictionary(g => g.Key, g => g.Key.LinkInstance().GetTotalTransform()); + foreach (var sp in spaces) { if (locationPoint.IsInSpace(sp)) return sp; } - // 4. Not found + // 3. Use room calculation point and check which space contains it + if (fi != null && fi.HasSpatialElementCalculationPoint && useRoomCalculationPoint) + { + if (fi.Space != null) + return fi.Space; + + XYZ roomCalcPoint = element.LocationPoint(useRoomCalculationPoint); + if (roomCalcPoint == null) + return null; + + if (!elementTransform.IsIdentity) + roomCalcPoint = elementTransform.OfPoint(roomCalcPoint); + + foreach (var sp in spaces) + { + if (roomCalcPoint.IsInSpace(sp)) + return sp; + } + } + + if (!findClosestIfNotContained) + return null; + + // 4. If not found, try find closest space in connector directions (for MEP elements) + var connectors = element.Connectors()?.OrderByDescending(x => x.GetMEPConnectorInfo().IsPrimary).ToList(); + if (connectors != null && connectors.Any()) + { + foreach (var conn in connectors) + { + XYZ connPoint = conn.Origin; + XYZ connDirection = conn.CoordinateSystem.BasisZ; + + if (!elementTransform.IsIdentity) + { + connPoint = elementTransform.OfPoint(connPoint); + connDirection = elementTransform.OfVector(connDirection); + } + + Space foundClosest = connPoint.FindClosestSpaceInDirection(connDirection, spaces, maxDistance: 3); // 3 feet max distance + if (foundClosest != null) + return foundClosest; + } + } + + // 5. If still not found, try find closest below (negative Z direction) + Space foundClosestBelow = locationPoint.FindClosestSpaceInDirection(-XYZ.BasisZ, spaces, maxDistance: 10); // 10 feet max distance + if (foundClosestBelow != null) + return foundClosestBelow; + + // Not found return null; } @@ -101,6 +146,38 @@ private static bool IsInSpace(this XYZ locationPoint, Space space) return space.IsPointInSpace(locationPoint); } + /***************************************************/ + + private static Space FindClosestSpaceInDirection(this XYZ startPoint, XYZ direction, IEnumerable spaces, double maxDistance) + { + if (startPoint == null || direction == null || spaces == null || maxDistance <= 0) + return null; + + // Ensure direction is normalized + XYZ normalizedDirection = direction.Normalize(); + + // Calculate step size and number of steps based on maxDistance + double stepSize = 1.0; // 1 feet step size + int maxSteps = (int)Math.Ceiling(maxDistance / stepSize); + + for (int step = 1; step <= maxSteps; step++) + { + double distance = step * stepSize; + if (distance > maxDistance) break; + + XYZ testPoint = startPoint.Add(normalizedDirection.Multiply(distance)); + + // Check if this point along the ray is in any space + foreach (Space space in spaces) + { + if (testPoint.IsInSpace(space)) + return space; + } + } + + return null; // No space found within max distance + } + /***************************************************/ /**** Private field ****/ /***************************************************/