From c191c649860eb23f0585ff068a833d0f705c391d Mon Sep 17 00:00:00 2001 From: Michal Pekacki Date: Fri, 20 Feb 2026 15:10:37 +0100 Subject: [PATCH 1/6] find nearest space mechanism implemented --- Revit_Core_Engine/Query/Space.cs | 78 ++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/Revit_Core_Engine/Query/Space.cs b/Revit_Core_Engine/Query/Space.cs index 0622a0ab7..03415e200 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; @@ -39,8 +40,9 @@ public static partial class Query [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; @@ -53,22 +55,17 @@ public static Space Space(this Element element, IEnumerable spaces, bool if (element is FamilyInstance fi && 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(); + 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()); + m_LinkTransforms = spaces.GroupBy(s => s.Document).Where(g => g.Key.IsLinked).ToDictionary(g => g.Key, g => g.Key.LinkInstance().GetTotalTransform()); } + // 3. Use location point and check which space contains it XYZ locationPoint = element.LocationPoint(useRoomCalculationPoint); if (locationPoint == null) return null; @@ -83,7 +80,36 @@ public static Space Space(this Element element, IEnumerable spaces, bool return sp; } - // 4. Not found + 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; + + // 6. Not found return null; } @@ -101,6 +127,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 ****/ /***************************************************/ From a2791548059abae952d695a3126ba086f26a8a25 Mon Sep 17 00:00:00 2001 From: Michal Pekacki Date: Mon, 23 Feb 2026 10:18:35 +0100 Subject: [PATCH 2/6] versioning attribute added --- Revit_Core_Engine/Query/Space.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Revit_Core_Engine/Query/Space.cs b/Revit_Core_Engine/Query/Space.cs index 03415e200..73e5bd88a 100644 --- a/Revit_Core_Engine/Query/Space.cs +++ b/Revit_Core_Engine/Query/Space.cs @@ -36,6 +36,7 @@ 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.")] From 91f1cdd8aea5a318f9db11047769b89f05faf71a Mon Sep 17 00:00:00 2001 From: Michal Pekacki Date: Thu, 26 Feb 2026 17:26:00 +0100 Subject: [PATCH 3/6] check against location point first --- Revit_Core_Engine/Query/Space.cs | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/Revit_Core_Engine/Query/Space.cs b/Revit_Core_Engine/Query/Space.cs index 73e5bd88a..f22bf69af 100644 --- a/Revit_Core_Engine/Query/Space.cs +++ b/Revit_Core_Engine/Query/Space.cs @@ -52,9 +52,7 @@ 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) - return fi.Space; + Transform elementTransform = element.Document.IsLinked ? element.Document.LinkInstance().GetTotalTransform() : Transform.Identity; if (spaces == null) { @@ -66,12 +64,11 @@ public static Space Space(this Element element, IEnumerable spaces, bool m_LinkTransforms = spaces.GroupBy(s => s.Document).Where(g => g.Key.IsLinked).ToDictionary(g => g.Key, g => g.Key.LinkInstance().GetTotalTransform()); } - // 3. Use location point and check which space contains it - XYZ locationPoint = element.LocationPoint(useRoomCalculationPoint); - if (locationPoint == null) + // 2. Check against physical location point of the element + XYZ locationPoint = element.LocationPoint(); + if (locationPoint == null) return null; - Transform elementTransform = element.Document.IsLinked ? element.Document.LinkInstance().GetTotalTransform() : Transform.Identity; if (!elementTransform.IsIdentity) locationPoint = elementTransform.OfPoint(locationPoint); @@ -81,6 +78,27 @@ public static Space Space(this Element element, IEnumerable spaces, bool return sp; } + // 2. If the element is a FamilyInstance, try the .Space property + if (element is FamilyInstance fi && fi.Space != null) + return fi.Space; + + // 3. Use location point with room calculation point and check which space contains it + if (useRoomCalculationPoint) + { + 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; From 4f4f207e129ca10bcd705f3d572e181bf07768ff Mon Sep 17 00:00:00 2001 From: Michal Pekacki Date: Thu, 26 Feb 2026 18:05:07 +0100 Subject: [PATCH 4/6] Improve space lookup for linked elements Rework Space lookup logic to better handle FamilyInstances and linked documents. Key changes: - Early return for FamilyInstance.Space when FI has no spatial calculation point. - Use element.LocationPoint(false) for primary location and apply linked-document transform to the point before containment tests. - Defer collecting spaces until after transforming the location point; still populate m_LinkTransforms for spaces from linked docs. - Only use room calculation point when FI has a spatial calculation point and useRoomCalculationPoint is true; prefer fi.Space when available. - Adjust comment numbering and preserve existing fallback strategies (connector-based nearest space and searching below). These changes ensure correct space resolution for elements coming from linked models and for family instances with or without calculation points. --- Revit_Core_Engine/Query/Space.cs | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Revit_Core_Engine/Query/Space.cs b/Revit_Core_Engine/Query/Space.cs index f22bf69af..791ea3b57 100644 --- a/Revit_Core_Engine/Query/Space.cs +++ b/Revit_Core_Engine/Query/Space.cs @@ -52,39 +52,39 @@ public static Space Space(this Element element, IEnumerable spaces, bool if (element is Space space) return space; - Transform elementTransform = element.Document.IsLinked ? element.Document.LinkInstance().GetTotalTransform() : Transform.Identity; - - 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()); - } + // 2. Check physical location - family instances without calculation point + FamilyInstance fi = element as FamilyInstance; + if (fi != null && !fi.HasSpatialElementCalculationPoint && fi.Space != null) + return fi.Space; - // 2. Check against physical location point of the element - XYZ locationPoint = element.LocationPoint(); + // 3. Check physical location - use 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; } - // 2. If the element is a FamilyInstance, try the .Space property - if (element is FamilyInstance fi && fi.Space != null) - return fi.Space; - - // 3. Use location point with room calculation point and check which space contains it - if (useRoomCalculationPoint) + // 4. 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; @@ -102,7 +102,7 @@ public static Space Space(this Element element, IEnumerable spaces, bool if (!findClosestIfNotContained) return null; - // 4. If not found, try find closest space in connector directions (for MEP elements) + // 5. 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()) { @@ -123,12 +123,12 @@ public static Space Space(this Element element, IEnumerable spaces, bool } } - // 5. If still not found, try find closest below (negative Z direction) + // 6. 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; - // 6. Not found + // Not found return null; } From e80c8d10a58b548d19f7b4b19c33dc8d55c737ac Mon Sep 17 00:00:00 2001 From: Michal Pekacki Date: Thu, 26 Feb 2026 18:15:00 +0100 Subject: [PATCH 5/6] cleanup --- Revit_Core_Engine/Query/Space.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Revit_Core_Engine/Query/Space.cs b/Revit_Core_Engine/Query/Space.cs index 791ea3b57..3cf8b161f 100644 --- a/Revit_Core_Engine/Query/Space.cs +++ b/Revit_Core_Engine/Query/Space.cs @@ -52,12 +52,12 @@ public static Space Space(this Element element, IEnumerable spaces, bool if (element is Space space) return space; - // 2. Check physical location - family instances without calculation point + // 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. Check physical location - use location point of the element + // 2b. Check physical location - location point of the element XYZ locationPoint = element.LocationPoint(false); if (locationPoint == null) return null; @@ -79,7 +79,7 @@ public static Space Space(this Element element, IEnumerable spaces, bool return sp; } - // 4. Use room calculation point and check which space contains it + // 3. Use room calculation point and check which space contains it if (fi != null && fi.HasSpatialElementCalculationPoint && useRoomCalculationPoint) { if (fi.Space != null) @@ -102,7 +102,7 @@ public static Space Space(this Element element, IEnumerable spaces, bool if (!findClosestIfNotContained) return null; - // 5. If not found, try find closest space in connector directions (for MEP elements) + // 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()) { @@ -123,7 +123,7 @@ public static Space Space(this Element element, IEnumerable spaces, bool } } - // 6. If still not found, try find closest below (negative Z direction) + // 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; From 70cb9c5f18705cd4bcde5ad9af7714326b949ecb Mon Sep 17 00:00:00 2001 From: Michal Pekacki Date: Wed, 4 Mar 2026 09:24:22 +0100 Subject: [PATCH 6/6] cleanup --- Revit_Core_Engine/Query/Space.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Revit_Core_Engine/Query/Space.cs b/Revit_Core_Engine/Query/Space.cs index 3cf8b161f..9c1c54d74 100644 --- a/Revit_Core_Engine/Query/Space.cs +++ b/Revit_Core_Engine/Query/Space.cs @@ -52,7 +52,7 @@ public static Space Space(this Element element, IEnumerable spaces, bool if (element is Space space) return space; - // 2a. Check physical location - space property of family without calculation point + // 2a. Check physical location - family without room calculation point FamilyInstance fi = element as FamilyInstance; if (fi != null && !fi.HasSpatialElementCalculationPoint && fi.Space != null) return fi.Space; @@ -117,16 +117,16 @@ public static Space Space(this Element element, IEnumerable spaces, bool connDirection = elementTransform.OfVector(connDirection); } - Space foundClosest = connPoint.FindClosestSpaceInDirection(connDirection, spaces, maxDistance: 3); // 3 feet max distance - if (foundClosest != null) - return foundClosest; + Space closestToConnector = connPoint.FindClosestSpaceInDirection(connDirection, spaces, maxDistance: 3); // 3 feet max distance + if (closestToConnector != null) + return closestToConnector; } } // 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; + Space closestBelow = locationPoint.FindClosestSpaceInDirection(-XYZ.BasisZ, spaces, maxDistance: 10); // 10 feet max distance + if (closestBelow != null) + return closestBelow; // Not found return null;