From e87eebefd1b263040d02ba3f0785ea451ebcdb85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Steinhagen?= <88777268+bjoernsteinhagen@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:13:17 +0200 Subject: [PATCH] fix(revit): prevent unpacking of internal unexpected elements in groups (#1266) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(revit): filter out sketch lines from grouped elements * fix(revit): exclude internal definitions (types/masses) from group unpacking * perf(revit): optimize group unpacking with FilteredElementCollector * chore(revit): remove unused method after refactor --------- Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com> --- .../HostApp/ElementUnpacker.cs | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/ElementUnpacker.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/ElementUnpacker.cs index f78622879..fd7e2eb73 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/ElementUnpacker.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/ElementUnpacker.cs @@ -9,8 +9,19 @@ namespace Speckle.Connectors.Revit.HostApp; /// public class ElementUnpacker { + private static readonly List s_skippedCategories = + [ + BuiltInCategory.OST_SketchLines, + BuiltInCategory.OST_MassForm, + BuiltInCategory.OST_StairsSketchBoundaryLines, + BuiltInCategory.OST_StairsSketchLandingCenterLines, + BuiltInCategory.OST_StairsSketchRiserLines, + BuiltInCategory.OST_RebarSketchLines, + BuiltInCategory.OST_StairsSketchRunLines + ]; + /// - /// Unpacks a random set of revit objects into atomic objects. It currently unpacks groups recurisvely, nested families into atomic family instances. + /// Unpacks a random set of revit objects into atomic objects. It currently unpacks groups recursively, nested families into atomic family instances. /// This method will also "pack" curtain walls if necessary (ie, if mullions or panels are selected without their parent curtain wall, they are sent independently; if the parent curtain wall is selected, they will be removed out as the curtain wall will include all its children). /// /// @@ -32,7 +43,7 @@ public class ElementUnpacker } /// - /// Unpacks input element ids into their subelements, eg groups and nested family instances + /// Unpacks input element ids into their sub-elements, eg groups and nested family instances /// /// /// @@ -57,11 +68,21 @@ public class ElementUnpacker // UNPACK: Groups if (element is Group g) { - // When a group is from a linked model, GetMemberIds may behave differently - // We add null checks to handle cases where elements can't be properly resolved - // POC: this might screw up generating hosting rel generation here, because nested families in groups get flattened out by GetMemberIds(). - var groupElements = g.GetMemberIds().Select(doc.GetElement).Where(el => el != null); - unpackedElements.AddRange(UnpackElements(groupElements, doc)); + var memberIds = g.GetMemberIds(); + + if (memberIds.Count <= 0) + { + continue; + } + + // using a collector more efficient + using var collector = new FilteredElementCollector(doc, memberIds); + collector.WhereElementIsNotElementType(); // exclude "Type" elements (FamilySymbols) + var filter = new ElementMulticategoryFilter(s_skippedCategories, inverted: true); // exclude "Sketch/Form" categories + collector.WherePasses(filter); + + // recursively unpack the valid results + unpackedElements.AddRange(UnpackElements(collector, doc)); } else if (element is BaseArray baseArray) {