From fad5c7c92101b2a3e1ce622a755d5270b06dc698 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Sat, 11 Mar 2023 11:29:13 +0500 Subject: [PATCH] Section annotation - custom markers / toggle displaying the markers Now it's possible to set custom markers in section annotation. To do so you need to add BBIM_Section pset to the annotation and then you can either toggle displaying some marker with ShowStartArrow/ShowEndArrow or you can write the id of some custom symbol in StartArrowSymbol/EndArrowSymbol (need to make sure the symbol is included in symbols.svg). Other changes: - removed vertex sorting in decoration.py so that start and end points will be the same thing both in svg and in viewport representation (previously the sorting was needed of the direction the arrow in shader wasn't inverted)); - fixed bug when in svg section arrow symbols wouldn't rotate if you move individual verts of the annotation, not the entire object (I guess this bug sneaked in after adapting elevation annotation) --- .../bim/data/pset/Psets_BBIM_Annotation.ifc | 4 +- .../blenderbim/bim/module/drawing/data.py | 51 ++++-- .../bim/module/drawing/decoration.py | 171 ++++++++++-------- .../bim/module/drawing/svgwriter.py | 130 +++++++------ 4 files changed, 217 insertions(+), 139 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc b/src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc index 5eb67995a3..0acd8088c8 100644 --- a/src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc +++ b/src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc @@ -14,9 +14,9 @@ DATA; #7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); #8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#9,#10,#11,#12,#13)); #9=IFCSIMPLEPROPERTYTEMPLATE('2a_9s8spHDc9dZHtgg71XL',$,'ShowStartArrow','Display start arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); -#10=IFCSIMPLEPROPERTYTEMPLATE('3i6SH_GbT7zhKea$wVE56A',$,'StartArrowSymbol','Custom symbol for the start of the section marker arrow.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); +#10=IFCSIMPLEPROPERTYTEMPLATE('3i6SH_GbT7zhKea$wVE56A',$,'StartArrowSymbol','Custom symbol for the start of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); #11=IFCSIMPLEPROPERTYTEMPLATE('1DtsPn5a9FG8$zXHDDMavY',$,'ShowEndArrow','Display end arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); -#12=IFCSIMPLEPROPERTYTEMPLATE('2$6U0mLI9AiPRWeBabdY3u',$,'EndArrowSymbol','Custom symbol for the end of the section marker arrow',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); +#12=IFCSIMPLEPROPERTYTEMPLATE('2$6U0mLI9AiPRWeBabdY3u',$,'EndArrowSymbol','Custom symbol for the end of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.); #13=IFCSIMPLEPROPERTYTEMPLATE('1naFqntIL7igCY7hCaE7kq',$,'HasConnectedSectionLine','Connect or disconnect section markers with line (by default = True).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.); ENDSEC; END-ISO-10303-21; diff --git a/src/blenderbim/blenderbim/bim/module/drawing/data.py b/src/blenderbim/blenderbim/bim/module/drawing/data.py index 1effddfb78..b5d6f9776e 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/data.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/data.py @@ -126,6 +126,7 @@ class SchedulesData: class DecoratorData: + # stores 1 type of data per object data = {} @classmethod @@ -142,22 +143,42 @@ class DecoratorData: return thickness @classmethod - def get_section_pset_data(cls, obj): + def get_section_markers_display_data(cls, obj): result = cls.data.get(obj.name, None) if result is not None: return result - element = tool.Ifc.get_entity(obj) - if element: - # default values - pset_data = { - "HasConnectedSectionLine": True, - "ShowStartArrow": True, - "StartArrowSymbol": "", - "ShowEndArrow": True, - "EndArrowSymbol": "", - } - obj_pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Section") or {} - pset_data.update(obj_pset_data) - cls.data[obj.name] = pset_data - return pset_data + element = tool.Ifc.get_entity(obj) + if not element: + return + + # default values + pset_data = { + "HasConnectedSectionLine": True, + "ShowStartArrow": True, + "StartArrowSymbol": "", + "ShowEndArrow": True, + "EndArrowSymbol": "", + } + obj_pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Section") or {} + pset_data.update(obj_pset_data) + + # create more usable display data + start_symbol = pset_data["StartArrowSymbol"] + end_symbol = pset_data["EndArrowSymbol"] + display_data = { + "start": { + "add_circle": pset_data["ShowStartArrow"] and not start_symbol, + "add_symbol": pset_data["ShowStartArrow"] or bool(start_symbol), + "symbol": start_symbol or "section-arrow", + }, + "end": { + "add_circle": pset_data["ShowEndArrow"] and not end_symbol, + "add_symbol": pset_data["ShowEndArrow"] or bool(end_symbol), + "symbol": end_symbol or "section-arrow", + }, + "connect_markers": pset_data["HasConnectedSectionLine"], + } + + cls.data[obj.name] = display_data + return display_data diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index 318c4351c7..7ddb8df279 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -1812,6 +1812,10 @@ class SectionDecorator(LevelDecorator): uniform vec2 winsize; uniform float viewportDrawingScale; uniform float connect_markers; + uniform float display_start_symbol; + uniform float display_end_symbol; + uniform float display_start_circle; + uniform float display_end_circle; layout(lines) in; layout(line_strip, max_vertices=MAX_POINTS) out; @@ -1835,7 +1839,7 @@ class SectionDecorator(LevelDecorator): vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1); vec4 edge = p1w - p0w, side = normalize(edge); vec4 gap = side * viewportDrawingScale * CIRCLE_SIZE; - vec4 dir = vec4(cross(vec3(side.xy, 0), vec3(0, 0, 1)).xy, 0, 0); + vec4 edge_ortho_dir = -vec4(cross(vec3(side.xy, 0), vec3(0, 0, 1)).xy, 0, 0); vec4 p; vec4 head[CIRCLE_SEGS]; @@ -1844,66 +1848,72 @@ class SectionDecorator(LevelDecorator): vec4 head5[5]; // start edge circle - for(int i=0; i 0) { + for(int i=0; i 0) { + // start edge triangle + triangle_head(side, edge_ortho_dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5); + p = p0w + head5[0]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p0w + head5[1]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p0w + head5[2]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p0w + head5[3]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p0w + head5[4]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + EndPrimitive(); + } // end edge circle - for(int i=0; i 0) { + for(int i=0; i 0) { + triangle_head(side, edge_ortho_dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5); + p = p1w + head5[0]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p1w + head5[1]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p1w + head5[2]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p1w + head5[3]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p1w + head5[4]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + EndPrimitive(); + } float divider_line_size; if (connect_markers > 0) { @@ -1912,30 +1922,39 @@ class SectionDecorator(LevelDecorator): divider_line_size = CIRCLE_SIZE * 3; } + // NOTE: basically we consider first vertex to be + // start and the second to be the end marker + // which is a bit inconsistent with svg + // but not that anyone will use multiple edge section annotation + // start reference divider - p = p0w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * divider_line_size; - gl_Position = WIN2CLIP(p); - EmitVertex(); - p = p0w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE; - gl_Position = WIN2CLIP(p); - EmitVertex(); - EndPrimitive(); + if (display_start_symbol > 0) { + p = p0w + side * viewportDrawingScale * divider_line_size; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p0w - side * viewportDrawingScale * CIRCLE_SIZE; + gl_Position = WIN2CLIP(p); + EmitVertex(); + EndPrimitive(); + } // end reference divider - p = p1w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE; - gl_Position = WIN2CLIP(p); - EmitVertex(); - p = p1w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * divider_line_size; - gl_Position = WIN2CLIP(p); - EmitVertex(); - EndPrimitive(); + if (display_end_symbol > 0) { + p = p1w + side * viewportDrawingScale * CIRCLE_SIZE; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p1w - side * viewportDrawingScale * divider_line_size; + gl_Position = WIN2CLIP(p); + EmitVertex(); + EndPrimitive(); + } // stem if (connect_markers > 0) { - p = p0w + gap; + p = p0w + (display_start_symbol > 0 ? gap : vec4(0)); gl_Position = WIN2CLIP(p); EmitVertex(); - p = p1w - gap; + p = p1w - (display_end_symbol > 0 ? gap : vec4(0)); gl_Position = WIN2CLIP(p); EmitVertex(); EndPrimitive(); @@ -1948,13 +1967,23 @@ class SectionDecorator(LevelDecorator): verts, idxs = self.get_editmesh_geom(obj) else: verts, idxs = self.get_mesh_geom(obj) - vert_map = {v.freeze(): (obj.matrix_world.inverted() @ v).x for v in verts} - verts = sorted(verts, key=lambda v: vert_map[v], reverse=True) + # TODO: add dashed line to shader # mind the vertices limit - section_pset_data = DecoratorData.get_section_pset_data(obj) - connect_markers = section_pset_data["HasConnectedSectionLine"] - self.draw_lines(context, obj, verts, idxs, extra_float_kwargs={"connect_markers": float(connect_markers)}) + display_data = DecoratorData.get_section_markers_display_data(obj) + self.draw_lines( + context, + obj, + verts, + idxs, + extra_float_kwargs={ + "connect_markers": float(display_data["connect_markers"]), + "display_start_symbol": float(display_data["start"]["add_symbol"]), + "display_end_symbol": float(display_data["end"]["add_symbol"]), + "display_start_circle": float(display_data["start"]["add_circle"]), + "display_end_circle": float(display_data["end"]["add_circle"]), + }, + ) class TextDecorator(BaseDecorator): diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index dc88c8ec53..5c2840f161 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -454,13 +454,17 @@ class SvgWriter: self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style)) elif predefined_type == "SECTION": - section_pset_data = DecoratorData.get_section_pset_data(obj) - connect_markers = section_pset_data["HasConnectedSectionLine"] + display_data = DecoratorData.get_section_markers_display_data(obj) + connect_markers = display_data["connect_markers"] + if connect_markers: for edge in obj.data.edges: draw_simple_edge_annotation(*edge.vertices[:]) else: for edge in obj.data.edges: + v0_marker_position = "start" if edge.vertices[0] == 0 else "end" + v1_marker_position = "start" if edge.vertices[1] == 0 else "end" + 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) @@ -472,20 +476,23 @@ class SvgWriter: circle_radius = 5 segment_size = circle_radius * 3 - self.svg.add( - self.svg.line( - start=start * self.svg_scale, - end=start * self.svg_scale + edge_dir * segment_size, - class_=" ".join(classes), + if display_data[v0_marker_position]["add_symbol"]: + self.svg.add( + self.svg.line( + start=start * self.svg_scale, + end=start * self.svg_scale + edge_dir * segment_size, + class_=" ".join(classes), + ) ) - ) - self.svg.add( - self.svg.line( - start=end * self.svg_scale, - end=end * self.svg_scale - edge_dir * segment_size, - class_=" ".join(classes), + + if display_data[v1_marker_position]["add_symbol"]: + self.svg.add( + self.svg.line( + start=end * self.svg_scale, + end=end * self.svg_scale - edge_dir * segment_size, + class_=" ".join(classes), + ) ) - ) else: for edge in obj.data.edges: @@ -507,58 +514,79 @@ class SvgWriter: self.draw_line_annotation(obj) - v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0))) - v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1))) - angle = -math.degrees((v2 - v1).xy.angle_signed(Vector((0, 1)))) + display_data = DecoratorData.get_section_markers_display_data(obj) - for vert in obj.data.vertices: - point = obj.matrix_world @ vert.co - symbol_position = self.project_point_onto_camera(point) - symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y))) - transform = "rotate({}, {}, {})".format( - angle, - (symbol_position * self.svg_scale)[0], - (symbol_position * self.svg_scale)[1], - ) + for edge in obj.data.edges: + edge_verts = [obj.data.vertices[v_i] for v_i in edge.vertices] - self.svg.add( - self.svg.use("#section-arrow", insert=tuple(symbol_position * self.svg_scale), transform=transform) - ) - self.svg.add(self.svg.use("#section-tag", insert=tuple(symbol_position * self.svg_scale))) + # convert edge vertiices coordinates to svg space + # to calculate the edge angle and for later use + edge_verts_svg = [] + for vert in edge_verts: + point = obj.matrix_world @ vert.co + symbol_position = self.project_point_onto_camera(point) + symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y))) + symbol_position_svg = symbol_position * self.svg_scale + edge_verts_svg.append(symbol_position_svg) - reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj)) - text_position = list(symbol_position * self.svg_scale) - text_style = { - "text-anchor": "middle", - "alignment-baseline": "middle", - "dominant-baseline": "middle", - } - self.svg.add(self.svg.text(reference_id, insert=(text_position[0], text_position[1] - 2.5), class_="SECTION", **text_style)) - self.svg.add(self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), class_="SECTION", **text_style)) + edge_dir = (edge_verts_svg[1] - edge_verts_svg[0]).normalized() + angle = degrees( edge_dir.xy.angle_signed( Vector([1,0]) ) ) + + for v_i, symbol_position_svg in zip(edge.vertices, edge_verts_svg): + current_marker_position = "start" if v_i == 0 else "end" + + # marker arrow symbol + if display_data[current_marker_position]["add_symbol"]: + transform = "rotate({}, {}, {})".format(angle, *symbol_position_svg.xy) + symbol_id = display_data[current_marker_position]["symbol"] + self.svg.add(self.svg.use(f"#{symbol_id}", insert=symbol_position_svg, transform=transform)) + + # marker circle and it's text + if display_data[current_marker_position]["add_circle"]: + self.svg.add(self.svg.use("#section-tag", insert=symbol_position_svg)) + + reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj)) + text_position = symbol_position_svg + text_style = { + "text-anchor": "middle", + "alignment-baseline": "middle", + "dominant-baseline": "middle", + } + self.svg.add( + self.svg.text( + reference_id, + insert=(text_position[0], text_position[1] - 2.5), + class_="SECTION", + **text_style, + ) + ) + self.svg.add( + self.svg.text( + sheet_id, + insert=(text_position[0], text_position[1] + 2.5), + class_="SECTION", + **text_style + ) + ) def draw_elevation_annotation(self, obj): x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 symbol_position = self.project_point_onto_camera(obj.location) symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y))) + symbol_position_svg = symbol_position * self.svg_scale v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0))) v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1))) angle = -math.degrees((v2 - v1).xy.angle_signed(Vector((0, 1)))) - transform = "rotate({}, {}, {})".format( - angle, - (symbol_position * self.svg_scale)[0], - (symbol_position * self.svg_scale)[1], - ) + transform = "rotate({}, {}, {})".format(angle, *symbol_position_svg.xy) - self.svg.add( - self.svg.use("#elevation-arrow", insert=tuple(symbol_position * self.svg_scale), transform=transform) - ) - self.svg.add(self.svg.use("#elevation-tag", insert=tuple(symbol_position * self.svg_scale))) + self.svg.add(self.svg.use("#elevation-arrow", insert=symbol_position_svg, transform=transform)) + self.svg.add(self.svg.use("#elevation-tag", insert=symbol_position_svg)) reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj)) - text_position = list(symbol_position * self.svg_scale) + text_position = symbol_position_svg text_style = { "text-anchor": "middle", "alignment-baseline": "middle", @@ -766,7 +794,7 @@ class SvgWriter: bm.verts.index_update() bm.edges.index_update() new_verts = [bm.verts.new(p) for p in points_chunk] - new_edges = [bm.edges.new( (new_verts[e[0]], new_verts[e[1]]) ) for e in ((0, 1), (1, 2))] + new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in ((0, 1), (1, 2))] self.draw_svg_3point_arc(obj, bm) def draw_svg_3point_arc(self, obj, bm): @@ -967,7 +995,7 @@ class SvgWriter: run = (B - O).length if run != 0: angle_tg = rise / run - angle = round( degrees( atan(angle_tg) )) + angle = round(degrees(atan(angle_tg))) else: angle = 90