From 972d70180107557e46c171aaefd7792392525416 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 9 Mar 2023 11:03:11 +0500 Subject: [PATCH] Fall annotations Added fall annotation - they appear both in viewport and svg. There are three types of annotations (SLOPE_ANGLE, SLOPE_FRACTION, SLOPE_PERCENT) you can switch them by changing annotation's ObjectType to one of those values. By default it uses angle representation. --- .../blenderbim/bim/data/styles/default.css | 6 +- .../blenderbim/bim/data/templates/markers.svg | 6 + .../bim/module/drawing/decoration.py | 137 +++++++++++++++++- .../bim/module/drawing/svgwriter.py | 80 +++++++++- .../blenderbim/bim/module/drawing/ui.py | 5 + .../blenderbim/bim/module/model/slab.py | 1 - src/blenderbim/blenderbim/tool/drawing.py | 1 + 7 files changed, 227 insertions(+), 9 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/data/styles/default.css b/src/blenderbim/blenderbim/bim/data/styles/default.css index 57ce10670f..78c9ac41b1 100644 --- a/src/blenderbim/blenderbim/bim/data/styles/default.css +++ b/src/blenderbim/blenderbim/bim/data/styles/default.css @@ -33,7 +33,11 @@ text { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT', .PredefinedType-PLANLEVEL { marker-end: url(#plan-level-marker); } .PredefinedType-DIMENSION { marker-start: url(#dimension-marker-start); marker-end: url(#dimension-marker-end); } .PredefinedType-ANGLE { marker-start: url(#angle-marker-start); marker-end: url(#angle-marker-end); } -.PredefinedType-RADIUS { marker-end: url(#radius-marker-end); } +.PredefinedType-RADIUS { marker-end: url(#fall-marker-end); } +.PredefinedType-FALL { marker-end: url(#radius-marker-end); } +.PredefinedType-SLOPEANGLE { marker-end: url(#radius-marker-end); } +.PredefinedType-SLOPEPERCENT { marker-end: url(#radius-marker-end); } +.PredefinedType-SLOPEFRACTION { marker-end: url(#radius-marker-end); } .PredefinedType-DIAMETER { marker-start: url(#diameter-marker-start); marker-end: url(#diameter-marker-end); } .PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; } .PredefinedType-STAIRARROW { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); } diff --git a/src/blenderbim/blenderbim/bim/data/templates/markers.svg b/src/blenderbim/blenderbim/bim/data/templates/markers.svg index 0726e59c88..cff4a43af3 100644 --- a/src/blenderbim/blenderbim/bim/data/templates/markers.svg +++ b/src/blenderbim/blenderbim/bim/data/templates/markers.svg @@ -19,6 +19,12 @@ + + + + + + diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index c998b2e886..066df06674 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -26,7 +26,7 @@ import bmesh import ifcopenshell import blenderbim.tool as tool import blenderbim.bim.module.drawing.helper as helper -from math import acos, pi +from math import acos, pi, atan, degrees from functools import reduce from itertools import chain from bpy.types import SpaceView3D @@ -236,12 +236,20 @@ class BaseDecorator: if element.is_a("IfcAnnotation"): if element.ObjectType == self.objecttype: results.append(obj) + elif ( self.objecttype == "MISC" and element.ObjectType not in decoration_presets and isinstance(obj.data, bpy.types.Mesh) ): results.append(obj) + + elif self.objecttype == "FALL" and element.ObjectType in ( + "SLOPE_ANGLE", + "SLOPE_FRACTION", + "SLOPE_PERCENT", + ): + results.append(obj) return results def get_path_geom(self, obj, topo=True): @@ -857,6 +865,125 @@ class RadiusDecorator(BaseDecorator): self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False) +class FallDecorator(BaseDecorator): + """Decorating text with arrows + - head point with arrow + """ + + objecttype = "FALL" + + DEF_GLSL = ( + BaseDecorator.DEF_GLSL + + """ + #define ARROW_ANGLE PI / 12.0 + #define ARROW_SIZE 16.0 + """ + ) + + GEOM_GLSL = """ + uniform vec2 winsize; + uniform float viewportDrawingScale; + + layout(lines) in; + layout(line_strip, max_vertices=MAX_POINTS) out; + in uint type[]; + + void main() { + vec4 clip2win = matCLIP2WIN(); + vec4 win2clip = matWIN2CLIP(); + + vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position; + uint t0 = type[0], t1 = type[1]; + + vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1); + vec4 edge = p1w - p0w, dir = normalize(edge); + vec4 gap1 = vec4(0); + + vec4 p; + + // end edge arrow for last segment + if (t1 == 2u) { + vec4 head[3]; + arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head); + + gl_Position = p1; + EmitVertex(); + p = p1w - head[1]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + p = p1w - head[2]; + gl_Position = WIN2CLIP(p); + EmitVertex(); + gl_Position = p1; + EmitVertex(); + EndPrimitive(); + + gap1 = dir * viewportDrawingScale * ARROW_SIZE; + } + + // stem, adjusted for and arrow + gl_Position = p0; + EmitVertex(); + p = p1w - gap1; + gl_Position = WIN2CLIP(p); + EmitVertex(); + EndPrimitive(); + } + """ + + def decorate(self, context, obj): + verts, idxs, topo = self.get_path_geom(obj) + self.draw_lines(context, obj, verts, idxs, topo) + self.draw_labels(context, obj) + + def draw_labels(self, context, obj): + region = context.region + region3d = context.region_data + dir = Vector((1, 0)) + pos = location_3d_to_region_2d(region, region3d, self.get_spline_end(obj)) + + spline = obj.data.splines[0] + spline_points = spline.bezier_points if spline.bezier_points else spline.points + + # generate label text + # same function as in svgwriter.py + def get_label_text(): + element = tool.Ifc.get_entity(obj) + B, A = [v.co.xyz for v in spline_points[:2]] + rise = abs(A.z - B.z) + O = A.copy() + O.z = B.z + run = (B - O).length + if run != 0: + angle_tg = rise / run + angle = round(degrees(atan(angle_tg))) + else: + angle = 90 + + # ues SLOPE_ANGLE as default + if element.ObjectType in ("FALL", "SLOPE_ANGLE"): + return f"{angle}°" + elif element.ObjectType == "SLOPE_FRACTION": + if angle == 90: + return "-" + return f"{self.format_value(context, rise)} / {self.format_value(context, run)}" + elif element.ObjectType == "SLOPE_PERCENT": + if angle == 90: + return "-" + return f"{round(angle_tg * 100)} %" + + if spline_points: + text = get_label_text() + self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False) + + def get_spline_end(self, obj): + spline = obj.data.splines[0] + spline_points = spline.bezier_points if spline.bezier_points else spline.points + if not spline_points: + return Vector((0, 0, 0)) + return obj.matrix_world @ spline_points[0].co + + class StairDecorator(BaseDecorator): """Decorating stairs - head point with arrow @@ -1444,7 +1571,7 @@ class BattingDecorator(BaseDecorator): verts, idxs = self.get_editmesh_geom(obj) else: verts, idxs = self.get_mesh_geom(obj) - + # TODO: find the less ugly way to figure thickness thickness = DecoratorData.get_batting_thickness(obj) region = context.region @@ -1461,9 +1588,8 @@ class BattingDecorator(BaseDecorator): obj, verts[:2], idxs, - extra_float_kwargs={ - "batting_thickness_winspace": winspace_thickness}, - is_scale_dependant = False + extra_float_kwargs={"batting_thickness_winspace": winspace_thickness}, + is_scale_dependant=False, ) @@ -1924,6 +2050,7 @@ class DecorationsHandler: ElevationDecorator, TextDecorator, BattingDecorator, + FallDecorator, ] installed = None diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index 98dd520eca..469b030f51 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, ceil +from math import pi, ceil, atan, degrees from mathutils import Vector from mathutils import geometry from blenderbim.bim.ifc import IfcStore @@ -162,6 +162,8 @@ class SvgWriter: self.draw_section_level_annotation(obj) elif element.ObjectType == "TEXT": self.draw_text_annotation(obj, obj.location) + elif element.ObjectType in ("FALL", "SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"): + self.draw_fall_annotations(obj) else: self.draw_misc_annotation(obj) @@ -884,12 +886,86 @@ class SvgWriter: "dominant-baseline": "middle", } - radius = ((matrix_world @ points[-1].co) - (matrix_world @ points[-2].co)).length + radius = (points[-1].co - points[-2].co).length radius = helper.format_distance(radius) tag = element.Description or f"R{radius}" self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style)) + def draw_fall_annotations(self, obj): + x_offset = self.raw_width / 2 + y_offset = self.raw_height / 2 + classes = self.get_attribute_classes(obj) + element = tool.Ifc.get_entity(obj) + matrix_world = obj.matrix_world + for spline in obj.data.splines: + points = self.get_spline_points(spline) + projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] + d = " ".join( + [ + "L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale) + for p in projected_points + ] + ) + d = "M{}".format(d[1:]) + path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes))) + + p0 = Vector( + ( + (x_offset + projected_points[0].x) * self.svg_scale, + (y_offset - projected_points[0].y) * self.svg_scale, + ) + ) + p1 = Vector( + ( + (x_offset + projected_points[1].x) * self.svg_scale, + (y_offset - projected_points[1].y) * self.svg_scale, + ) + ) + + # generate label text + # same function as in decoration.py + def get_label_text(): + B, A = [v.co.xyz for v in points[:2]] + rise = abs(A.z - B.z) + O = A.copy() + O.z = B.z + run = (B - O).length + if run != 0: + angle_tg = rise / run + angle = round( degrees( atan(angle_tg) )) + else: + angle = 90 + + # ues SLOPE_ANGLE as default + if element.ObjectType in ("FALL", "SLOPE_ANGLE"): + return f"{angle}°" + elif element.ObjectType == "SLOPE_FRACTION": + if angle == 90: + return "-" + return f"{helper.format_distance(rise)} / {helper.format_distance(run)}" + elif element.ObjectType == "SLOPE_PERCENT": + if angle == 90: + return "-" + return f"{round(angle_tg * 100)} %" + + tag = element.Description or get_label_text() + + text_offset = (p0 - p1).xy.normalized() * len(tag) + text_position = projected_points[0] + text_position = Vector( + ((x_offset + text_position.x) * self.svg_scale, (y_offset - text_position.y) * self.svg_scale) + ) + text_position += text_offset + + text_style = { + "text-anchor": "middle", + "alignment-baseline": "middle", + "dominant-baseline": "middle", + } + + self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style)) + def draw_diameter_annotations(self, obj): classes = self.get_attribute_classes(obj) matrix_world = obj.matrix_world diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index a22f04e010..592f68db97 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -425,6 +425,11 @@ class BIM_PT_annotation_utilities(Panel): op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE") op.object_type = "FILL_AREA" + row = layout.row(align=True) + op = row.operator("bim.add_annotation", text="Fall", icon="SORT_ASC") + op.object_type = "FALL" + op.data_type = "curve" + row = layout.row(align=True) row.prop(self.props, "should_draw_decorations", text="Viewport Annotations") row.enabled = context.scene.camera is not None diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py index cbc07c849c..8f0372dd30 100644 --- a/src/blenderbim/blenderbim/bim/module/model/slab.py +++ b/src/blenderbim/blenderbim/bim/module/model/slab.py @@ -612,7 +612,6 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator): bpy.ops.object.mode_set(mode="EDIT") ProfileDecorator.install(context) - # TODO: test it from properties panel? if not bpy.app.background: bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool") return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index cdd7b56e4e..3cf5a58d0d 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -53,6 +53,7 @@ class Drawing(blenderbim.core.tool.Drawing): "BREAKLINE": "mesh", "DIAMETER": "curve", "DIMENSION": "curve", + "FALL": "curve", "FILL_AREA": "mesh", "HIDDEN_LINE": "mesh", "LINEWORK": "mesh",