Revision Clouds - new annotation type

Short demonstration - https://github.com/IfcOpenShell/IfcOpenShell/assets/9417531/90c9b458-0bae-40a2-9eee-d748e57c24a3

Curently it doesn't have any special representation in viewport and it doesn't have any customisable parameters.

You might get come across error "Revision cloud edges must form just 1 polygon" - it means your edges either don't form any polygon or form 2 or more, both cases are not supported.
This commit is contained in:
Andrej730
2023-05-27 15:22:07 +05:00
parent adec3853e8
commit 1d2c9780da
6 changed files with 144 additions and 41 deletions
@@ -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
@@ -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,
@@ -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"),
}
@@ -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[:])
@@ -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")
+8 -18
View File
@@ -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)