From 540e4c9cee73ef50401f1eb8cfe4dc8b271fad5c Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Mon, 20 Jul 2026 02:41:09 +0300 Subject: [PATCH] bonsai: allow overriding which classes join in section linework (#4395) (#8617) Fixes #4395. Root cause: the SVG cut-linework merge step that fuses adjacent elements' cut polygons together (per the pset-driven JoinCriteria setting) was hardcoded to only IfcWall and IfcSlab. IfcCovering cut shapes were skipped unconditionally, so adjacent coverings never joined, leaving a visible seam/broken corner in section drawings regardless of JoinCriteria. Fix: added an EPset_Drawing.JoinClasses property, following the exact same user-overridable pattern already used by EPset_Drawing.BringToFront - a comma-separated list of IFC classes to join, defaulting to "IfcWall,IfcSlab" (unchanged behavior) when unset. Users can override per-drawing to add IfcCovering (or any other class) when they want it joined too. Kept this opt-in rather than hardcoding IfcCovering into the default list, since joining a thin finish layer the same way as a thick wall/slab could produce unwanted mitring in some cases - the user decides per drawing. Verified live against the reporter's own attached file (ifcovering joining.ifc) and its cached section linework: with JoinClasses unset, two separate closed paths reproduce the reported seam exactly. With JoinClasses = "IfcWall,IfcSlab,IfcCovering", the two coverings merge into a single closed polygon with the internal seam removed. Confirmed IfcSlab join behavior is unchanged in both runs. Generated with the assistance of an AI coding tool. Co-authored-by: Dion Moult (cherry picked from commit c55a79b8b5da688c8c3af608782e9f98896808d0) --- src/bonsai/bonsai/bim/data/pset/EPset_Drawing.ifc | 3 ++- src/bonsai/bonsai/bim/module/drawing/operator.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/bonsai/bonsai/bim/data/pset/EPset_Drawing.ifc b/src/bonsai/bonsai/bim/data/pset/EPset_Drawing.ifc index 4d8bc1ad47..aa39cc353b 100644 --- a/src/bonsai/bonsai/bim/data/pset/EPset_Drawing.ifc +++ b/src/bonsai/bonsai/bim/data/pset/EPset_Drawing.ifc @@ -5,7 +5,7 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',$,$,'EPset_Drawing','EPset_D FILE_SCHEMA(('IFC4')); ENDSEC; DATA; -#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation/DRAWING',(#23,#22,#27,#24,#29,#30,#19,#12,#26,#9,#8,#7,#6,#4,#18,#11,#5,#20,#25,#14,#10,#17,#28,#16,#3,#21,#13,#15,#2,#31,#32,#33,#34,#35,#36)); +#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation/DRAWING',(#23,#22,#27,#24,#29,#30,#19,#12,#26,#9,#8,#7,#6,#4,#18,#11,#5,#20,#25,#14,#10,#17,#28,#16,#3,#21,#13,#15,#2,#31,#32,#33,#34,#35,#36,#37)); #2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); #3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); #4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); @@ -41,5 +41,6 @@ DATA; #34=IFCSIMPLEPROPERTYTEMPLATE('2epSGfC4bFM9gb1X7zBIp4',$,'RenderSharp','Whether to render ''sharp'' (convex) edges. Only relevant when UseEdgeClassification is enabled.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); #35=IFCSIMPLEPROPERTYTEMPLATE('3TZwsEjkr5WRDKcgrYzSIA',$,'RidgeAngleMinDegrees','Minimum convex dihedral deviation from flat, in degrees, for a projection edge to be classified as ''sharp'' rather than ''flush''.',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.); #36=IFCSIMPLEPROPERTYTEMPLATE('2Jua$lO754vgZOkBoHM2gA',$,'RenderFlush','Whether to render ''flush'' edges (dihedral deviation below both ridge/valley thresholds). Only relevant when UseEdgeClassification is enabled.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); +#37=IFCSIMPLEPROPERTYTEMPLATE('1zM9sia2L8RQDnWZxgUwlZ',$,'JoinClasses','Comma separated list of IFC classes whose cut linework will be joined together when they meet (e.g. mitred at a corner).\X2\000A\X0\Defaults to ''IfcWall,IfcSlab'' if not set. Override to also join other classes, such as ''IfcWall,IfcSlab,IfcCovering''.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.); ENDSEC; END-ISO-10303-21; diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index 25c1d07f77..7308408968 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -1454,6 +1454,15 @@ class CreateDrawing(bpy.types.Operator): "Material.Name", ] + join_classes = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "JoinClasses") + if join_classes: + join_classes = tuple(c.strip() for c in join_classes.split(",") if c.strip()) + else: + # Architectural convention only merges these objects by default. E.g. pipe + # segments and fittings shouldn't merge. Users may override this per-drawing + # via the EPset_Drawing.JoinClasses property (e.g. to also join IfcCovering). + join_classes = ("IfcWall", "IfcSlab") + group = root.find("{http://www.w3.org/2000/svg}g") joined_paths = {} self.is_manifold_cache = {} @@ -1555,8 +1564,7 @@ class CreateDrawing(bpy.types.Operator): ) path.attrib["d"] = d - # Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge. - if not element.is_a("IfcWall") and not element.is_a("IfcSlab"): + if not any(element.is_a(c) for c in join_classes): continue keys = []