diff --git a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py index 8eae554b15..ac6130e604 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/annotation.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/annotation.py @@ -20,6 +20,7 @@ import bpy import os import blenderbim.tool as tool from mathutils import Vector +import bmesh class Annotator: @@ -94,7 +95,7 @@ class Annotator: return obj @staticmethod - def add_plane_to_annotation(obj): + def add_plane_to_annotation(obj, remove_face=False): # default order = bot left, top left, bot right, top right # therefore we redefine the order face_verts = [0, 2, 3, 1] @@ -104,7 +105,9 @@ class Annotator: bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True) new_verts = [bm.verts.new(v) for v in verts_local] - bm.faces.new([new_verts[i] for i in face_verts]) + face = bm.faces.new([new_verts[i] for i in face_verts]) + if remove_face: + bmesh.ops.delete(bm, geom=[face], context="FACES_ONLY") tool.Blender.apply_bmesh(obj.data, bm, obj) return obj diff --git a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py index d4e30af08f..9554e5ac18 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/decoration.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/decoration.py @@ -343,7 +343,7 @@ class BaseDecorator: shader.uniform_float("color", color) batch.draw(shader) - def draw_lines(self, context, obj, vertices, indices, color=None): + def draw_lines(self, context, obj, vertices, indices, color=None, line_width=1.0): """`verts` should be in winspace with `(0,0,0)` in the screen left bottom corner, not in the center""" region = context.region if not color: @@ -352,7 +352,7 @@ class BaseDecorator: self.line_shader.bind() # POLYLINE_UNIFORM_COLOR specific uniforms self.line_shader.uniform_float("viewportSize", (region.width, region.height)) - self.line_shader.uniform_float("lineWidth", 1.0) + self.line_shader.uniform_float("lineWidth", line_width) gpu.state.blend_set("ALPHA") self.draw_batch("LINES", vertices, color, indices) @@ -1010,6 +1010,19 @@ class MiscDecorator(BaseDecorator): self.draw_lines(context, obj, winspace_verts, idxs) +class RevisionCloudDecorator(BaseDecorator): + objecttype = "REVISION_CLOUD" + + def decorate(self, context, obj): + if obj.data.is_editmode: + verts, idxs = self.get_editmesh_geom(obj) + else: + verts, idxs = self.get_mesh_geom(obj) + winspace_verts = worldspace_to_winspace(verts, context) + # TODO: draw revision clouds inside viewport? + self.draw_lines(context, obj, winspace_verts, idxs, color=(1, 0, 0, 1), line_width=2) + + # TODO: custom frag shader to support dashed lines? class HiddenDecorator(BaseDecorator): objecttype = "HIDDEN_LINE" @@ -1883,6 +1896,7 @@ class DecorationsHandler: PlanLevelDecorator, SectionLevelDecorator, StairDecorator, + RevisionCloudDecorator, BreakDecorator, SectionDecorator, ElevationDecorator, diff --git a/src/blenderbim/blenderbim/bim/module/drawing/prop.py b/src/blenderbim/blenderbim/bim/module/drawing/prop.py index 97cbcf2d4f..5358bab396 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/prop.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/prop.py @@ -274,11 +274,10 @@ class DrawingStyle(PropertyGroup): attributes: CollectionProperty(name="Attributes", type=StrProperty) - class RasterStyleProperty(enum.Enum): # EVAL_PROP_ props will be evaluated explicitly EVAL_PROP_WORLD_COLOR = "bpy.data.worlds[0].color" - + # those props attributes used as a source for shading style properties RENDER = "scene.render" VIEW_SETTINGS = "scene.view_settings" @@ -487,6 +486,7 @@ ANNOTATION_TYPES_DATA = { "BREAKLINE": ("Breakline", "", "FCURVE", "mesh"), "LINEWORK": ("Line", "", "MESH_MONKEY", "mesh"), "BATTING": ("Batting", "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set", "FORCE_FORCE", "mesh"), + "REVISION_CLOUD":("Revision Cloud", "Add revision cloud", "VOLUME_DATA", "mesh"), "FILL_AREA": ("Fill Area", "", "NODE_TEXTURE", "mesh"), "FALL": ("Fall", "", "SORT_ASC", "curve"), } diff --git a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py index bcbf5dc8f7..0ab437359c 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/svgwriter.py @@ -403,8 +403,7 @@ class SvgWriter: self.svg.line(start=start * self.svg_scale, end=end * self.svg_scale, class_=" ".join(classes)) ) - # BATTING ANNOTATIONS - if predefined_type == "BATTING": + def draw_batting_annotation(): 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) @@ -470,7 +469,94 @@ class SvgWriter: ) self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style)) - elif predefined_type == "SECTION": + def draw_revision_cloud_annotation(): + segment_width = 15.0 + base_height = 1 + width = 5 + + def get_svg_half_circle(height, width): + cp0 = f"0,-{height}" + cp1 = f"{width},-{height}" + end_point = f"{width},0" + circle = f"c{cp0} {cp1} {end_point}" + return circle + + def get_revision_pattern(base_offset): + pattern = f"m{base_offset.x},{base_offset.y}" + pattern += " " + get_svg_half_circle(2 * base_height, width) + pattern += " " + get_svg_half_circle(2.5 * base_height, width) + pattern += " " + get_svg_half_circle(1.5 * base_height, width) + return pattern + + def get_scale(size, direction): + vector = direction * size + shrinked_vector = size // segment_width * segment_width * direction + scale = [1 if vector[i] == 0 else vector[i] / shrinked_vector[i] for i in range(2)] + return "scale(%f, %f)" % (scale[0], scale[1]) + + def poly_to_edges(poly): + edges = [] + n_verts = len(poly) + lats_index = n_verts - 1 + for i in range(len(poly)): + edge = [poly[i], (poly[i + 1]) if i != lats_index else poly[0]] + edges.append(edge) + return edges + + element = tool.Ifc.get_entity(obj) + safe_offset_x = 2.0 + marker_width = segment_width + safe_offset_x * 2 + market_height = 15.0 + ref_y = 5.0 + revision_pattern = get_revision_pattern(Vector([safe_offset_x, ref_y])) + + bm = tool.Blender.get_bmesh_for_mesh(obj.data).copy() + bmesh.ops.contextual_create(bm, geom=bm.edges[:]) + faces = bm.faces[:] + assert len(faces) == 1, "Revision cloud edges must form just 1 polygon" + + # ensure clockwise order of polygon verts + # given default blender counter-clockwise order + polygon = faces[0] + if polygon.normal.z > 0: + polygon.normal_flip() + + marker_id = f"revision-cloud-{element.GlobalId}" + svg_path = self.svg.path(style="fill: none; stroke:red; stroke-width:0.20", d=revision_pattern) + marker = self.svg.marker( + markerUnits="userSpaceOnUse", + insert=(safe_offset_x, ref_y), + size=(marker_width, market_height), + orient="auto", + id=marker_id, + ) + marker.add(svg_path) + self.svg.add(marker) + + for v0, v1 in poly_to_edges(polygon.verts): + v0_global = matrix_world @ v0.co.xyz + v1_global = matrix_world @ v1.co.xyz + v0 = self.project_point_onto_camera(v0_global) + v1 = self.project_point_onto_camera(v1_global) + 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 + + pattern_edge = end_svg - start_svg + pattern_dir = pattern_edge.normalized() + pattern_length = pattern_edge.length + + segments = int(pattern_length // segment_width) + pattern_dir_step = pattern_dir * segment_width + points = [pattern_dir_step * i for i in range(segments)] + + polyline_style = f"marker: url(#{marker_id}); stroke: none;" + polyline_transform = f"translate({start_svg.x}, {start_svg.y}) {get_scale(pattern_length, pattern_dir)}" + polyline = self.svg.polyline( + points=points, class_=" ".join(classes), style=polyline_style, transform=polyline_transform + ) + self.svg.add(polyline) + + def draw_section_annotation(): display_data = DecoratorData.get_section_markers_display_data(obj) connect_markers = display_data["connect_markers"] @@ -511,6 +597,12 @@ class SvgWriter: ) ) + if predefined_type == "BATTING": + draw_batting_annotation() + elif predefined_type == "REVISIONCLOUD": + draw_revision_cloud_annotation() + elif predefined_type == "SECTION": + draw_section_annotation() else: for edge in obj.data.edges: draw_simple_edge_annotation(*edge.vertices[:]) diff --git a/src/blenderbim/blenderbim/bim/module/drawing/ui.py b/src/blenderbim/blenderbim/bim/module/drawing/ui.py index b4a9ba8723..24d10c8add 100644 --- a/src/blenderbim/blenderbim/bim/module/drawing/ui.py +++ b/src/blenderbim/blenderbim/bim/module/drawing/ui.py @@ -27,6 +27,7 @@ from blenderbim.bim.module.drawing.data import ( DrawingsData, DecoratorData, ) +from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA class BIM_PT_camera(Panel): @@ -471,55 +472,55 @@ class BIM_PT_annotation_utilities(Panel): row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Dimension", icon="FIXED_SIZE") op.object_type = "DIMENSION" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op = row.operator("bim.add_annotation", text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE") op.object_type = "ANGLE" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Radius", icon="FORWARD") op.object_type = "RADIUS" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op = row.operator("bim.add_annotation", text="Diameter", icon="ARROW_LEFTRIGHT") op.object_type = "DIAMETER" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS") op.object_type = "TEXT" - op.data_type = "empty" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS") op.object_type = "TEXT_LEADER" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK") op.object_type = "STAIR_ARROW" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO") op.object_type = "HIDDEN_LINE" - op.data_type = "mesh" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT") op.object_type = "PLAN_LEVEL" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN") op.object_type = "SECTION_LEVEL" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE") op.object_type = "BREAKLINE" - op.data_type = "mesh" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op = row.operator("bim.add_annotation", text="Line", icon="MESH_MONKEY") op.object_type = "LINEWORK" - op.data_type = "mesh" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Batting", icon="FORCE_FORCE") op.object_type = "BATTING" - op.data_type = "mesh" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) op.description = "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set" op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE") op.object_type = "FILL_AREA" @@ -527,7 +528,10 @@ class BIM_PT_annotation_utilities(Panel): row = layout.row(align=True) op = row.operator("bim.add_annotation", text="Fall", icon="SORT_ASC") op.object_type = "FALL" - op.data_type = "curve" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) + op = row.operator("bim.add_annotation", text="Revision", icon="VOLUME_DATA") + op.object_type = "REVISION_CLOUD" + op.data_type = tool.Drawing.get_annotation_data_type(op.object_type) row = layout.row(align=True) row.prop(self.props, "should_draw_decorations", text="Viewport Annotations") diff --git a/src/blenderbim/blenderbim/tool/drawing.py b/src/blenderbim/blenderbim/tool/drawing.py index bd3a5b78b1..c10abf326f 100644 --- a/src/blenderbim/blenderbim/tool/drawing.py +++ b/src/blenderbim/blenderbim/tool/drawing.py @@ -37,7 +37,7 @@ import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.helper as helper from blenderbim.bim.module.drawing.data import FONT_SIZES, DecoratorData -from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT_POSITIONS +from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT_POSITIONS, ANNOTATION_TYPES_DATA from mathutils import Vector import collections @@ -55,28 +55,18 @@ class Drawing(blenderbim.core.tool.Drawing): tool.Ifc.get(), source.Representation, exclude=["IfcGeometricRepresentationContext"] ) + @classmethod + def get_annotation_data_type(cls, object_type): + return ANNOTATION_TYPES_DATA[object_type][3] + @classmethod def create_annotation_object(cls, drawing, object_type): - data_type = { - "ANGLE": "curve", - "BATTING": "mesh", - "BREAKLINE": "mesh", - "DIAMETER": "curve", - "DIMENSION": "curve", - "FALL": "curve", - "FILL_AREA": "mesh", - "HIDDEN_LINE": "mesh", - "LINEWORK": "mesh", - "PLAN_LEVEL": "curve", - "RADIUS": "curve", - "SECTION_LEVEL": "curve", - "STAIR_ARROW": "curve", - "TEXT": "empty", - "TEXT_LEADER": "curve", - }[object_type] + data_type = cls.get_annotation_data_type(object_type) obj = annotation.Annotator.get_annotation_obj(drawing, object_type, data_type) if object_type == "FILL_AREA": obj = annotation.Annotator.add_plane_to_annotation(obj) + elif object_type == "REVISION_CLOUD": + obj = annotation.Annotator.add_plane_to_annotation(obj, remove_face=True) elif object_type == "TEXT_LEADER": co1, _, co2, _ = annotation.Annotator.get_placeholder_coords() obj = annotation.Annotator.add_line_to_annotation(obj, co2, co1)