mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
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:
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user