From f7b9a7bfb59508682132f84ab207d5b706e230e7 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 12 May 2023 14:54:01 +0500 Subject: [PATCH] Multiline in dimension description / suffix / prefix #3005 Also added some offset for dimension text in viewport to make it more similar to svg --- .../bim/module/drawing/decoration.py | 65 +++++++++++++-- .../bim/module/drawing/svgwriter.py | 80 ++++++++++++++++--- 2 files changed, 128 insertions(+), 17 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index cac6757536..fce45b1195 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -377,17 +377,42 @@ class BaseDecorator: font_size_mm=2.5, line_no=0, box_alignment=None, + multiline=False, + multiline_to_bottom=True, ): """Draw text label Args: pos: bottom-center + multiline: \n characters will be interpreted as line breaks aligned and centered at segment middle NOTE: `blf.draw` seems to ignore the \n character, so we have to split the text ourselves and use the `line_no` argument of `draw_label` + """ + + if multiline: + line_no = 0 + lines = text.split("\\n") + lines = lines if multiline_to_bottom else lines[::-1] + for line in lines: + self.draw_label( + context, + line, + pos, + text_dir, + gap=gap, + center=center, + vcenter=vcenter, + font_size_mm=font_size_mm, + line_no=line_no, + box_alignment=box_alignment, + ) + line_no += 1 if multiline_to_bottom else -1 + return + # 0 is the default font, but we're fancier than that font_id = self.font_id @@ -492,7 +517,7 @@ class BaseDecorator: start_i = add_verts_sequence(add_offsets(v0, segment), start_i, **out_kwargs) self.draw_lines(context, obj, output_verts, output_edges) - def draw_text(self, context, obj, text_world_position=None): + def draw_text(self, context, obj, text_world_position=None, reverse_lines_order=False): """if `text_world_position` is not provided, the object's location will be used""" if not text_world_position: text_world_position = obj.location @@ -522,6 +547,7 @@ class BaseDecorator: text_scale = obj.scale.x line_i = 0 + font_size_mm = text_data["FontSize"] * text_scale for literal_data in literals_data: box_alignment = literal_data["BoxAlignment"] @@ -534,11 +560,11 @@ class BaseDecorator: gap=0, center=False, vcenter=False, - font_size_mm=text_data["FontSize"] * text_scale, + font_size_mm=font_size_mm, line_no=line_i, box_alignment=box_alignment, ) - line_i += 1 + line_i += 1 if not reverse_lines_order else -1 class DimensionDecorator(BaseDecorator): @@ -624,6 +650,8 @@ class DimensionDecorator(BaseDecorator): show_description_only = dimension_data["show_description_only"] text_prefix = dimension_data["text_prefix"] text_suffix = dimension_data["text_suffix"] + viewportDrawingScale = self.get_viewport_drawing_scale(context) + text_offset_value = viewportDrawingScale * 3 for i0, i1 in indices: v0 = Vector(vertices[i0]) @@ -633,6 +661,8 @@ class DimensionDecorator(BaseDecorator): text_dir = p1 - p0 if text_dir.length < 1: continue + perpendicular = Vector((-text_dir.y, text_dir.x)).normalized() + text_offset = perpendicular * text_offset_value if not show_description_only: length = (v1 - v0).length @@ -641,12 +671,35 @@ class DimensionDecorator(BaseDecorator): text = self.format_value(context, length) text = text_prefix + text + text_suffix - self.draw_label(context, text, p0 + text_dir * 0.5, text_dir, box_alignment="bottom-middle") + self.draw_label( + context, + text, + p0 + text_dir * 0.5 + text_offset, + text_dir, + box_alignment="bottom-middle", + multiline=True, + multiline_to_bottom=False, + ) if description: - self.draw_label(context, description, p0 + text_dir * 0.5, text_dir, box_alignment="top-middle") + self.draw_label( + context, + description, + p0 + text_dir * 0.5 - text_offset, + text_dir, + box_alignment="top-middle", + multiline=True, + ) elif show_description_only and description: - self.draw_label(context, description, p0 + text_dir * 0.5, text_dir, box_alignment="bottom-middle") + self.draw_label( + context, + description, + p0 + text_dir * 0.5 + text_offset, + text_dir, + box_alignment="bottom-middle", + multiline=True, + multiline_to_bottom=False, + ) class AngleDecorator(BaseDecorator): diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index 31ce948602..fdb8ce1277 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -1149,28 +1149,86 @@ class SvgWriter: # 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)))) + angle = math.degrees(vector.angle_signed(Vector((1, 0)))) 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)} + if not show_description_only: + text = f"{text_prefix}{str(dimension)}{text_suffix}" + text_tag = self.create_text_tag( + text, + text_position + perpendicular, + angle, + "bottom-middle", + "DIMENSION", + text_format=text_format, + multiline=True, + multiline_to_bottom=False, + ) + self.svg.add(text_tag) + if dimension_text: + text_tag = self.create_text_tag( + dimension_text, + text_position - perpendicular, + angle, + "top-middle", + "DIMENSION", + text_format=text_format, + multiline=True, + multiline_to_bottom=True, + ) + self.svg.add(text_tag) + + elif show_description_only and dimension_text: + text_tag = self.create_text_tag( + dimension_text, + text_position + perpendicular, + angle, + "bottom-middle", + "DIMENSION", + text_format=text_format, + multiline=True, + multiline_to_bottom=False, + ) + self.svg.add(text_tag) + + def create_text_tag( + self, + text, + text_position, + angle, + box_alignment, + class_str, + text_format=lambda x: x, + multiline=False, + multiline_to_bottom=False, + ): + if not multiline: + text_kwargs = {"transform": "rotate({} {} {})".format(angle, text_position.x, text_position.y)} return self.svg.text( text_format(text), insert=text_position, - class_="DIMENSION", + class_=class_str, **(text_kwargs | SvgWriter.get_box_alignment_parameters(box_alignment)), ) - if not show_description_only: - text = f"{text_prefix}{str(dimension)}{text_suffix}" - self.svg.add(create_text_tag(text, text_position + perpendicular, "bottom-middle")) - if dimension_text: - self.svg.add(create_text_tag(dimension_text, text_position - perpendicular, "top-middle")) + text_position_svg_str = ", ".join(map(str, text_position)) + text_transform = f"translate({text_position_svg_str}) rotate({angle})" + text_kwargs = { + "transform": text_transform, + "style": "font-size: 0;", + } - elif show_description_only and dimension_text: - self.svg.add(create_text_tag(dimension_text, text_position + perpendicular, "bottom-middle")) + text_tag = self.svg.text("", **text_kwargs, **SvgWriter.get_box_alignment_parameters(box_alignment)) + text_lines = text.replace("\\n", "\n").split("\n") + text_lines = text_lines if multiline_to_bottom else text_lines[::-1] + + for line_number, text_line in enumerate(text_lines): + tspan = self.svg.tspan(text_format(text_line), class_=class_str, insert=(0, 0)) + tspan.update({"dy": f"{line_number if multiline_to_bottom else -line_number}em"}) + text_tag.add(tspan) + return text_tag def project_point_onto_camera(self, point): # TODO is this needlessly complex?