From f5b6fdcb5b0c000b6352e81086e885170cf8d2e3 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 27 Feb 2023 18:38:04 +0500 Subject: [PATCH] Batting Annotation pattern Added batting pattern to line annotations. The way to use it is: 1) Add "Line" Annotation; 2) Change object type to "BATTING" 3) Add object property set "Pset_BBIM_Bating" (also added in this commit) to specify the batting thickness. Currently there is no preview for batting in viewport. Other changes: - EPset_Annotation.ifc -> Psets_BBIM_Annotation.ifc (more general psets ifc library for the future use) --- ...notation.ifc => Psets_BBIM_Annotation.ifc} | 3 + .../blenderbim/bim/data/styles/default.css | 1 - .../blenderbim/bim/data/templates/markers.svg | 4 +- .../blenderbim/bim/module/drawing/operator.py | 3 +- .../bim/module/drawing/svgwriter.py | 101 +++++++++++++++--- src/blenderbim/blenderbim/core/tool.py | 1 + src/blenderbim/blenderbim/tool/pset.py | 5 + src/blenderbim/test/bim/test_feature.py | 8 ++ 8 files changed, 106 insertions(+), 20 deletions(-) rename src/blenderbim/blenderbim/bim/data/pset/{EPset_Annotation.ifc => Psets_BBIM_Annotation.ifc} (60%) diff --git a/src/blenderbim/blenderbim/bim/data/pset/EPset_Annotation.ifc b/src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc similarity index 60% rename from src/blenderbim/blenderbim/bim/data/pset/EPset_Annotation.ifc rename to src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc index 8aabdfd00b..1cb611443f 100644 --- a/src/blenderbim/blenderbim/bim/data/pset/EPset_Annotation.ifc +++ b/src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc @@ -9,5 +9,8 @@ DATA; #2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.); #3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); #4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); +#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'Pset_BBIM_Batting','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#6,#7)); +#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.); +#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); ENDSEC; END-ISO-10303-21; diff --git a/src/blenderbim/blenderbim/bim/data/styles/default.css b/src/blenderbim/blenderbim/bim/data/styles/default.css index c6897a9777..2f0fa462e5 100644 --- a/src/blenderbim/blenderbim/bim/data/styles/default.css +++ b/src/blenderbim/blenderbim/bim/data/styles/default.css @@ -37,7 +37,6 @@ text { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT'; .PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; } .PredefinedType-STAIRARROW { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); } .PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; } -.PredefinedType-BATTING { marker-start: url(#batting); stroke: none; } .PredefinedType-SEALANT { fill: url(#crosshatch1); stroke-width: 0.25; } .PredefinedType-FILLAREA { fill: white; stroke: black; } .PredefinedType-BREAKLINE { fill: none; stroke: black; stroke-width: 0.25; marker-mid: url(#breakline-marker); } diff --git a/src/blenderbim/blenderbim/bim/data/templates/markers.svg b/src/blenderbim/blenderbim/bim/data/templates/markers.svg index 87ba553db2..0726e59c88 100644 --- a/src/blenderbim/blenderbim/bim/data/templates/markers.svg +++ b/src/blenderbim/blenderbim/bim/data/templates/markers.svg @@ -1,9 +1,7 @@ - - - + diff --git a/src/blenderbim/blenderbim/bim/module/drawing/operator.py b/src/blenderbim/blenderbim/bim/module/drawing/operator.py index 20e5c2ae38..239087092c 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/operator.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/operator.py @@ -137,8 +137,7 @@ class CreateDrawing(bpy.types.Operator): Only available if : - IFC file is created - - Camera is in Orthographic mode - """ + - Camera is in Orthographic mode""" bl_idname = "bim.create_drawing" bl_label = "Create Drawing" diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index e5c7907fdd..b3da8b10bb 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -31,7 +31,7 @@ import ifcopenshell.util.representation import blenderbim.tool as tool import blenderbim.bim.module.drawing.helper as helper import blenderbim.bim.module.drawing.annotation as annotation -from math import pi +from math import pi, ceil from mathutils import Vector from mathutils import geometry from blenderbim.bim.ifc import IfcStore @@ -363,22 +363,95 @@ class SvgWriter: self.draw_edge_annotation(obj, classes) def draw_edge_annotation(self, obj, classes): - x_offset = self.raw_width / 2 - y_offset = self.raw_height / 2 - matrix_world = obj.matrix_world - for edge in obj.data.edges: - v0_global = matrix_world @ obj.data.vertices[edge.vertices[0]].co.xyz - v1_global = matrix_world @ obj.data.vertices[edge.vertices[1]].co.xyz + predefined_type = classes[2] + + if predefined_type.endswith("-BATTING"): + x_offset = self.raw_width / 2 + y_offset = self.raw_height / 2 + matrix_world = obj.matrix_world + + v0_global = matrix_world @ obj.data.vertices[0].co.xyz + v1_global = matrix_world @ obj.data.vertices[1].co.xyz v0 = self.project_point_onto_camera(v0_global) v1 = self.project_point_onto_camera(v1_global) - start = Vector(((x_offset + v0.x), (y_offset - v0.y))) - end = Vector(((x_offset + v1.x), (y_offset - v1.y))) - vector = end - start - line = self.svg.add( - self.svg.line( - start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes) - ) + start_svg = Vector(((x_offset + v0.x), (y_offset - v0.y))) * self.svg_scale + end_svg = Vector(((x_offset + v1.x), (y_offset - v1.y))) * self.svg_scale + + element = tool.Ifc.get_entity(obj) + pset_data = tool.Pset.get_element_pset_data(element, "Pset_BBIM_Batting") or {} + + # SVG uses mm as default unit + conversion_to_mm = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) / 0.001 + thickness = pset_data["Thickness"] * conversion_to_mm if "Thickness" in pset_data else 15.0 + reverse_x = pset_data.get("Reverse pattern direction", False) + + if reverse_x: + start_svg, end_svg = end_svg, start_svg + + pattern_dir = (end_svg - start_svg).normalized() + pattern_length = (end_svg - start_svg).length + segment_width = thickness / 2.5 + + segments = ceil(pattern_length / segment_width) + end_marker_width = pattern_length - segment_width * (segments - 1) + + points = [start_svg + pattern_dir * segment_width * i for i in range(segments)] + marker_id = f"batting-{element.GlobalId}" + marker_end_id = f"batting-end-{element.GlobalId}" + path_data = f"""M 0 {0.2*thickness} + A {0.5*segment_width} {0.2*thickness} 0 0 1 {segment_width} {0.2*thickness} + L {0.5*segment_width} {0.8*thickness} + M 0 {0.2*thickness} + L {0.5*segment_width} {0.8*thickness} + A {0.5*segment_width} {0.2*thickness} 0 0 0 {segment_width} {1.0*thickness} + M {0.5*segment_width} {0.8*thickness} + A {0.5*segment_width} {0.2*thickness} 0 0 1 0 {1.0*thickness} + """ + path_data = " ".join(path_data.split()) + + svg_path = self.svg.path(style="fill: none; stroke:black; stroke-width:0.18", d=path_data) + if reverse_x: + svg_path.update({"transform": f"scale(-1,-1) translate(-{segment_width}, -{thickness})"}) + + marker = self.svg.marker( + markerUnits="userSpaceOnUse", + insert=(0, thickness / 2), + size=(segment_width, thickness), + orient="auto", + id=marker_id, ) + marker.add(svg_path) + self.svg.add(marker) + + # separate marker for the end of the pattern to truncate it more gracefully + marker_end = marker.copy() + marker_end.update({"markerWidth": end_marker_width, "id": marker_end_id}) + self.svg.add(marker_end) + + polyline_style = ( + f"marker-start: url(#{marker_id}); " + f"marker-mid: url(#{marker_id}); stroke: none; " + f"marker-end: url(#{marker_end_id}); stroke: none; " + ) + self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style)) + + else: + x_offset = self.raw_width / 2 + y_offset = self.raw_height / 2 + matrix_world = obj.matrix_world + for edge in obj.data.edges: + v0_global = matrix_world @ obj.data.vertices[edge.vertices[0]].co.xyz + v1_global = matrix_world @ obj.data.vertices[edge.vertices[1]].co.xyz + v0 = self.project_point_onto_camera(v0_global) + v1 = self.project_point_onto_camera(v1_global) + start = Vector(((x_offset + v0.x), (y_offset - v0.y))) + end = Vector(((x_offset + v1.x), (y_offset - v1.y))) + vector = end - start + line = self.svg.add( + self.svg.line( + start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes) + ) + ) def draw_leader_annotation(self, obj): self.draw_line_annotation(obj) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index bda034c753..5a674e44fd 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -486,6 +486,7 @@ class Profile: @interface class Pset: def get_element_pset(cls, element, pset_name): pass + def get_element_pset_data(cls, element, pset_name): pass @interface diff --git a/src/blenderbim/blenderbim/tool/pset.py b/src/blenderbim/blenderbim/tool/pset.py index 707ef8102f..0e21d2d28d 100644 --- a/src/blenderbim/blenderbim/tool/pset.py +++ b/src/blenderbim/blenderbim/tool/pset.py @@ -29,3 +29,8 @@ class Pset(blenderbim.core.tool.Pset): pset = psets.get(pset_name, None) if pset: return tool.Ifc.get().by_id(pset["id"]) + + @classmethod + def get_element_pset_data(cls, element, pset_name): + psets = ifcopenshell.util.element.get_psets(element) + return psets.get(pset_name, None) diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index 3a139236a7..faf0f830b3 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -707,9 +707,17 @@ def hit_undo(): bpy.ops.ed.undo_push(message="UNDO STEP") bpy.ops.ed.undo() + # These definitions are not to be used in tests but simply in debugging failing tests +@given(parsers.parse("I run test code")) +@when(parsers.parse("I run test code")) +@then(parsers.parse("I run test code")) +def run_test_code(): + pass + + @given(parsers.parse("I save sample test files")) @when(parsers.parse("I save sample test files")) @then(parsers.parse("I save sample test files"))