From f6a95e8c17db2ac8e928fcd70aaaceab318355e7 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 14 Apr 2023 16:04:05 +0500 Subject: [PATCH] Dimension description in viewport/option to hide the measurements #2918 same thing for diameter annotations since they are just reusing dimension annotations + some refactor along the way --- .../bim/data/pset/Psets_BBIM_Annotation.ifc | 2 + .../blenderbim/bim/module/drawing/data.py | 16 +- .../bim/module/drawing/decoration.py | 25 +- .../blenderbim/bim/module/drawing/prop.py | 6 +- .../bim/module/drawing/svgwriter.py | 221 ++++++++---------- 5 files changed, 134 insertions(+), 136 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 1478bcfba2..4497fa8de3 100644 --- a/src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc +++ b/src/blenderbim/blenderbim/bim/data/pset/Psets_BBIM_Annotation.ifc @@ -27,5 +27,7 @@ DATA; #20=IFCSIMPLEPROPERTYTEMPLATE('23tejOrxj859R_eCRp1AFP',$,'MarkersPath','Default markers SVG',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.); #21=IFCSIMPLEPROPERTYTEMPLATE('1UDakJ5_f7kBhggNSW4$h5',$,'SymbolsPath','Default symbols SVG',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.); #22=IFCSIMPLEPROPERTYTEMPLATE('0d53LEtgLDQxnv__NfgH7i',$,'PatternsPath','Default patterns SVG',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.); +#23=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#24)); +#24=IFCSIMPLEPROPERTYTEMPLATE('1rL2AbQsXD8RbpoWH5pYOV',$,'ShowDescriptionOnly','Hide the measurement values and show only annotation description',.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 2911449977..c96d08450e 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/data.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/data.py @@ -259,15 +259,16 @@ class DecoratorData: cls.data[obj.name] = text_data return text_data - # used by Ifc Annotations with ObjectType = "DIMENSION" + # used by Ifc Annotations with ObjectType = "DIMENSION" / "DIAMETER" @classmethod - def get_dimension_style(cls, obj): + def get_dimension_data(cls, obj): result = cls.data.get(obj.name, None) if result is not None: return result element = tool.Ifc.get_entity(obj) - if not element or not element.is_a("IfcAnnotation") or element.ObjectType != "DIMENSION": + supported_object_types = ("DIMENSION", "DIAMETER") + if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in supported_object_types: return None dimension_style = "arrow" @@ -275,8 +276,13 @@ class DecoratorData: if classes and "oblique" in classes.lower().split(): dimension_style = "oblique" - cls.data[obj.name] = dimension_style - return dimension_style + show_description_only = ( + ifcopenshell.util.element.get_pset(element, "BBIM_Dimension", "ShowDescriptionOnly") or False + ) + + dimension_data = {"dimension_style": dimension_style, "show_description_only": show_description_only} + cls.data[obj.name] = dimension_data + return dimension_data class AnnotationData: diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index 6fa5f4ebc3..b72547a500 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -523,7 +523,7 @@ class DimensionDecorator(BaseDecorator): def decorate(self, context, obj): verts, idxs, _ = self.get_path_geom(obj, topo=False) - dimension_style = DecoratorData.get_dimension_style(obj) + dimension_style = DecoratorData.get_dimension_data(obj)["dimension_style"] self.draw_lines( context, obj, verts, idxs, extra_float_kwargs={"use_oblique_style": float(dimension_style == "oblique")} ) @@ -532,17 +532,30 @@ class DimensionDecorator(BaseDecorator): def draw_labels(self, context, obj, vertices, indices): region = context.region region3d = context.region_data + + element = tool.Ifc.get_entity(obj) + description = element.Description + show_description_only = DecoratorData.get_dimension_data(obj)["show_description_only"] + for i0, i1 in indices: v0 = Vector(vertices[i0]) v1 = Vector(vertices[i1]) p0 = location_3d_to_region_2d(region, region3d, v0) p1 = location_3d_to_region_2d(region, region3d, v1) - dir = p1 - p0 - if dir.length < 1: + text_dir = p1 - p0 + if text_dir.length < 1: continue - length = (v1 - v0).length - text = self.format_value(context, length) - self.draw_label(context, text, p0 + (dir) * 0.5, dir) + + if not show_description_only: + length = (v1 - v0).length + text = self.format_value(context, length) + + self.draw_label(context, text, p0 + text_dir * 0.5, text_dir, box_alignment="bottom-middle") + if description: + self.draw_label(context, description, p0 + text_dir * 0.5, text_dir, box_alignment="top-middle") + + elif show_description_only and description: + self.draw_label(context, description, p0 + text_dir * 0.5, text_dir, box_alignment="bottom-middle") class AngleDecorator(BaseDecorator): diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 961652bf9f..429e49864c 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -454,11 +454,12 @@ class BIMAssignedProductProperties(PropertyGroup): # ObjectType: annotation_name, description, icon, data_type +# fmt: off ANNOTATION_TYPES_DATA = { - "DIMENSION": ("Dimension", "", "FIXED_SIZE", "curve"), + "DIMENSION": ("Dimension", "Add dimensions annotation.\nMeasurement values can be hidden through ShowDescriptionOnly property\nof BBIM_Dimension property set", "FIXED_SIZE", "curve"), "ANGLE": ("Angle", "", "DRIVER_ROTATIONAL_DIFFERENCE", "curve"), "RADIUS": ("Radius", "", "FORWARD", "curve"), - "DIAMETER": ("Diameter", "", "ARROW_LEFTRIGHT", "curve"), + "DIAMETER": ("Diameter", "Add diameter annotation.\nMeasurement values can be hidden through ShowDescriptionOnly property\nof BBIM_Dimension property set", "ARROW_LEFTRIGHT", "curve"), "TEXT": ("Text", "", "SMALL_CAPS", "empty"), "TEXT_LEADER": ("Leader", "", "TRACKING_BACKWARDS", "curve"), "STAIR_ARROW": ("Stair Arrow", "Add stair arrow annotation.\nIf you have IfcStairFlight object selected, it will be used as a reference for the annotation", "SCREEN_BACK", "curve"), @@ -471,6 +472,7 @@ ANNOTATION_TYPES_DATA = { "FILL_AREA": ("Fill Area", "", "NODE_TEXTURE", "mesh"), "FALL": ("Fall", "", "SORT_ASC", "curve"), } +# fmt: on annotation_classes = [(x, *ANNOTATION_TYPES_DATA[x][:3], i) for i, x in enumerate(ANNOTATION_TYPES_DATA)] diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index d053569e72..0b24ea09c4 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -233,11 +233,7 @@ class SvgWriter: rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl) rl *= unit_scale rl = "{:.3f}m".format(rl) - text_style = { - "text-anchor": "start", - "alignment-baseline": "baseline", - "dominant-baseline": "baseline", - } + text_style = self.get_box_alignment_parameters("bottom-left") self.svg.add(self.svg.text(f"RL +{rl}", insert=tuple(text_position), class_="SECTIONLEVEL", **text_style)) if tag: self.svg.add(self.svg.text(tag, insert=(text_position[0], text_position[1] - 5), **text_style)) @@ -266,11 +262,7 @@ class SvgWriter: "UP", insert=tuple(text_position), class_="STAIR", - **{ - "text-anchor": "middle", - "alignment-baseline": "middle", - "dominant-baseline": "middle", - }, + **self.get_box_alignment_parameters("center"), ) ) @@ -294,16 +286,13 @@ class SvgWriter: ) line["stroke-dasharray"] = "12.5, 3, 3, 3" axis_tag = tool.Ifc.get_entity(obj).Name + text_style = self.get_box_alignment_parameters("center") self.svg.add( self.svg.text( axis_tag, insert=tuple(start * self.svg_scale), class_="GRID", - **{ - "text-anchor": "middle", - "alignment-baseline": "middle", - "dominant-baseline": "middle", - }, + **text_style, ) ) self.svg.add( @@ -311,11 +300,7 @@ class SvgWriter: axis_tag, insert=tuple(end * self.svg_scale), class_="GRID", - **{ - "text-anchor": "middle", - "alignment-baseline": "middle", - "dominant-baseline": "middle", - }, + **text_style, ) ) @@ -571,11 +556,7 @@ class SvgWriter: 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", - } + text_style = self.get_box_alignment_parameters("center") self.svg.add( self.svg.text( reference_id, @@ -608,11 +589,7 @@ class SvgWriter: 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", - } + text_style = self.get_box_alignment_parameters("center") self.svg.add( self.svg.text( reference_id, insert=(text_position[0], text_position[1] - 2.5), class_="ELEVATION", **text_style @@ -639,6 +616,43 @@ class SvgWriter: return (reference_id, sheet_id) return ("-", "-") + def get_box_alignment_parameters(self, box_alignment): + """Convenience method to get svg parameters for text alignment + in a readable way. + + Metehod expecting values like: + + `top-left`, `top-middle`, `top-right`, + + `middle-left`, `center`, `middle-right`, + + `bottom-left`, `bottom-middle`, `bottom-right` + """ + # reference for alignment values: + # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor + # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline + vertical_alignment = { + "top": "hanging", + "bottom": "baseline", + "center": "middle", + "middle": "middle", + } + alignment_baseline = vertical_alignment[next(align for align in vertical_alignment if align in box_alignment)] + horizontal_alignment = { + "left": "start", + "right": "end", + "center": "middle", + "middle": "middle", + } + text_anchor = horizontal_alignment[next(align for align in horizontal_alignment if align in box_alignment)] + + # using dominant-baseline because we plan to use subtags + # otherwise alignment-baseline would be sufficient + return { + "dominant-baseline": alignment_baseline, + "text-anchor": text_anchor, + } + def draw_text_annotation(self, text_obj, position): x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 @@ -684,34 +698,6 @@ class SvgWriter: if not symbol_svg or not template_text_fields: self.svg.add(self.svg.use(f"#{symbol}", insert=text_position_svg)) - def get_box_alignment_parameters(box_alignment): - # reference for alignment values: - # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor - # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline - vertical_alignment = { - "top": "hanging", - "bottom": "baseline", - "center": "middle", - "middle": "middle", - } - alignment_baseline = vertical_alignment[ - next(align for align in vertical_alignment if align in box_alignment) - ] - horizontal_alignment = { - "left": "start", - "right": "end", - "center": "middle", - "middle": "middle", - } - text_anchor = horizontal_alignment[next(align for align in horizontal_alignment if align in box_alignment)] - - # using dominant-baseline because we plan to use subtags - # otherwise alignment-baseline would be sufficient - return { - "dominant-baseline": alignment_baseline, - "text-anchor": text_anchor, - } - for text_literal in text_literals: # after pretty indentation some redundant spaces can occur in svg tags # this is why we apply "font-size: 0;" to the text tag to remove those spaces @@ -725,7 +711,7 @@ class SvgWriter: "transform": transform, "style": "font-size: 0;", }, - **get_box_alignment_parameters(text_literal.BoxAlignment), + **self.get_box_alignment_parameters(text_literal.BoxAlignment), ) self.svg.add(text_tag) @@ -803,20 +789,15 @@ class SvgWriter: rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl) rl *= unit_scale rl = "{:.3f}m".format(rl) - if projected_points[0].x > projected_points[-1].x: - text_anchor = "end" - else: - text_anchor = "start" + + box_alignment = "bottom-left" if projected_points[0].x <= projected_points[-1].x else "bottom-right" + text_style = self.get_box_alignment_parameters(box_alignment) self.svg.add( self.svg.text( "RL +{}".format(rl), insert=tuple(text_position), class_="PLANLEVEL", - **{ - "text-anchor": text_anchor, - "alignment-baseline": "baseline", - "dominant-baseline": "baseline", - }, + **text_style, ) ) @@ -932,12 +913,7 @@ class SvgWriter: text_offset = (text_position - center_position).xy.normalized() * 5 text_position += text_offset - text_style = { - "text-anchor": "middle", - "alignment-baseline": "middle", - "dominant-baseline": "middle", - } - + text_style = self.get_box_alignment_parameters("center") angle_text = abs(round(math.degrees(angle), 3)) if is_reflex: angle_text = 360 - angle_text @@ -998,12 +974,7 @@ class SvgWriter: ) text_position += text_offset - text_style = { - "text-anchor": "middle", - "alignment-baseline": "middle", - "dominant-baseline": "middle", - } - + text_style = self.get_box_alignment_parameters("center") radius = (points[-1].co - points[-2].co).length radius = helper.format_distance(radius, precision=self.precision) tag = element.Description or f"R{radius}" @@ -1076,19 +1047,15 @@ class SvgWriter: ) text_position += text_offset - text_style = { - "text-anchor": "middle", - "alignment-baseline": "middle", - "dominant-baseline": "middle", - } - + text_style = self.get_box_alignment_parameters("center") 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 element = tool.Ifc.get_entity(obj) - text_override = element.Description + dimension_text = element.Description + show_description_only = DecoratorData.get_dimension_data(obj)["show_description_only"] for spline in obj.data.splines: points = self.get_spline_points(spline) for i, p in enumerate(points): @@ -1097,22 +1064,32 @@ class SvgWriter: v0_global = matrix_world @ points[i].co.xyz v1_global = matrix_world @ points[i + 1].co.xyz self.draw_dimension_annotation( - v0_global, v1_global, classes, text_override, text_format=lambda x: "D" + x + v0_global, + v1_global, + classes, + dimension_text, + text_format=lambda x: "D" + x, + show_description_only=show_description_only, ) def draw_dimension_annotations(self, obj): classes = self.get_attribute_classes(obj) matrix_world = obj.matrix_world element = tool.Ifc.get_entity(obj) - text_override = element.Description + dimension_text = element.Description + show_description_only = DecoratorData.get_dimension_data(obj)["show_description_only"] for spline in obj.data.splines: points = self.get_spline_points(spline) - for i, p in enumerate(points): - if i + 1 >= len(points): - continue + for i in range(len(points) - 1): v0_global = matrix_world @ points[i].co.xyz v1_global = matrix_world @ points[i + 1].co.xyz - self.draw_dimension_annotation(v0_global, v1_global, classes, text_override) + self.draw_dimension_annotation( + v0_global, + v1_global, + classes, + dimension_text=dimension_text, + show_description_only=show_description_only, + ) def draw_measureit_arch_dimension_annotations(self): try: @@ -1126,46 +1103,44 @@ class SvgWriter: Vector(coord[0]), Vector(coord[1]), ["IfcAnnotation", "PredefinedType-DIMENSION"] ) - def draw_dimension_annotation(self, v0_global, v1_global, classes, text_override=None, text_format=lambda x: x): - x_offset = self.raw_width / 2 - y_offset = self.raw_height / 2 + def draw_dimension_annotation( + self, v0_global, v1_global, classes, dimension_text=None, text_format=lambda x: x, show_description_only=False + ): + offset = Vector([self.raw_width, self.raw_height]) / 2 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))) + start = (offset + v0.xy * Vector((1, -1))) * self.svg_scale + end = (offset + v1.xy * Vector((1, -1))) * self.svg_scale mid = ((end - start) / 2) + start vector = end - start perpendicular = Vector((vector.y, -vector.x)).normalized() dimension = (v1_global - v0_global).length dimension = helper.format_distance(dimension, precision=self.precision) - sheet_dimension = ((end * self.svg_scale) - (start * self.svg_scale)).length - if sheet_dimension < 5: # annotation can't fit - # offset text to right of marker - text_position = (end * self.svg_scale) + perpendicular + (3 * vector.normalized()) - else: - text_position = (mid * self.svg_scale) + perpendicular + sheet_dimension = (end - start).length + + # if annotation can't fit offset text to the right of marker + text_position = mid if sheet_dimension > 5 else (end + (3 * vector.normalized())) rotation = math.degrees(vector.angle_signed(Vector((1, 0)))) - line = self.svg.add( - self.svg.line( - start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes) - ) - ) - if text_override is not None: - text = text_override - else: - text = str(dimension) - text = text_format(text) - self.svg.add( - self.svg.text( - text, - insert=tuple(text_position), + + line = self.svg.line(start=start, end=end, class_=" ".join(classes)) + self.svg.add(line) + + def create_text_tag(text, text_position, box_alignment): + text_kwargs = {"transform": "rotate({} {} {})".format(rotation, text_position.x, text_position.y)} + return self.svg.text( + text_format(text), + insert=text_position, class_="DIMENSION", - **{ - "transform": "rotate({} {} {})".format(rotation, text_position.x, text_position.y), - "text-anchor": "middle", - }, + **(text_kwargs | self.get_box_alignment_parameters(box_alignment)), ) - ) + + if not show_description_only: + self.svg.add(create_text_tag(str(dimension), text_position + perpendicular, "bottom-middle")) + if dimension_text: + self.svg.add(create_text_tag(dimension_text, text_position - perpendicular, "top-middle")) + + elif show_description_only and dimension_text: + self.svg.add(create_text_tag(dimension_text, text_position + perpendicular, "bottom-middle")) def project_point_onto_camera(self, point): # TODO is this needlessly complex?