Fall annotations

Added fall annotation - they appear both in viewport and svg.

There are three types of annotations (SLOPE_ANGLE, SLOPE_FRACTION, SLOPE_PERCENT) you can switch them by changing annotation's ObjectType to one of those values.

By default it uses angle representation.
This commit is contained in:
Andrej730
2023-03-09 11:03:11 +05:00
parent cdee5532b6
commit 972d701801
7 changed files with 227 additions and 9 deletions
@@ -33,7 +33,11 @@ text { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type B TT',
.PredefinedType-PLANLEVEL { marker-end: url(#plan-level-marker); } .PredefinedType-PLANLEVEL { marker-end: url(#plan-level-marker); }
.PredefinedType-DIMENSION { marker-start: url(#dimension-marker-start); marker-end: url(#dimension-marker-end); } .PredefinedType-DIMENSION { marker-start: url(#dimension-marker-start); marker-end: url(#dimension-marker-end); }
.PredefinedType-ANGLE { marker-start: url(#angle-marker-start); marker-end: url(#angle-marker-end); } .PredefinedType-ANGLE { marker-start: url(#angle-marker-start); marker-end: url(#angle-marker-end); }
.PredefinedType-RADIUS { marker-end: url(#radius-marker-end); } .PredefinedType-RADIUS { marker-end: url(#fall-marker-end); }
.PredefinedType-FALL { marker-end: url(#radius-marker-end); }
.PredefinedType-SLOPEANGLE { marker-end: url(#radius-marker-end); }
.PredefinedType-SLOPEPERCENT { marker-end: url(#radius-marker-end); }
.PredefinedType-SLOPEFRACTION { marker-end: url(#radius-marker-end); }
.PredefinedType-DIAMETER { marker-start: url(#diameter-marker-start); marker-end: url(#diameter-marker-end); } .PredefinedType-DIAMETER { marker-start: url(#diameter-marker-start); marker-end: url(#diameter-marker-end); }
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; } .PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
.PredefinedType-STAIRARROW { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); } .PredefinedType-STAIRARROW { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
@@ -19,6 +19,12 @@
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" /> <path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
</g> </g>
</marker> </marker>
<!-- same as for the radius -->
<marker id="fall-marker-end" markerHeight="14" markerWidth="11" orient="auto" refX="10" refY="7">
<g>
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
</g>
</marker>
<marker id="angle-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7"> <marker id="angle-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
<g> <g>
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" /> <path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

@@ -26,7 +26,7 @@ import bmesh
import ifcopenshell import ifcopenshell
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.module.drawing.helper as helper import blenderbim.bim.module.drawing.helper as helper
from math import acos, pi from math import acos, pi, atan, degrees
from functools import reduce from functools import reduce
from itertools import chain from itertools import chain
from bpy.types import SpaceView3D from bpy.types import SpaceView3D
@@ -236,12 +236,20 @@ class BaseDecorator:
if element.is_a("IfcAnnotation"): if element.is_a("IfcAnnotation"):
if element.ObjectType == self.objecttype: if element.ObjectType == self.objecttype:
results.append(obj) results.append(obj)
elif ( elif (
self.objecttype == "MISC" self.objecttype == "MISC"
and element.ObjectType not in decoration_presets and element.ObjectType not in decoration_presets
and isinstance(obj.data, bpy.types.Mesh) and isinstance(obj.data, bpy.types.Mesh)
): ):
results.append(obj) results.append(obj)
elif self.objecttype == "FALL" and element.ObjectType in (
"SLOPE_ANGLE",
"SLOPE_FRACTION",
"SLOPE_PERCENT",
):
results.append(obj)
return results return results
def get_path_geom(self, obj, topo=True): def get_path_geom(self, obj, topo=True):
@@ -857,6 +865,125 @@ class RadiusDecorator(BaseDecorator):
self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False) self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False)
class FallDecorator(BaseDecorator):
"""Decorating text with arrows
- head point with arrow
"""
objecttype = "FALL"
DEF_GLSL = (
BaseDecorator.DEF_GLSL
+ """
#define ARROW_ANGLE PI / 12.0
#define ARROW_SIZE 16.0
"""
)
GEOM_GLSL = """
uniform vec2 winsize;
uniform float viewportDrawingScale;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
in uint type[];
void main() {
vec4 clip2win = matCLIP2WIN();
vec4 win2clip = matWIN2CLIP();
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
uint t0 = type[0], t1 = type[1];
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, dir = normalize(edge);
vec4 gap1 = vec4(0);
vec4 p;
// end edge arrow for last segment
if (t1 == 2u) {
vec4 head[3];
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
gl_Position = p1;
EmitVertex();
p = p1w - head[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w - head[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
gl_Position = p1;
EmitVertex();
EndPrimitive();
gap1 = dir * viewportDrawingScale * ARROW_SIZE;
}
// stem, adjusted for and arrow
gl_Position = p0;
EmitVertex();
p = p1w - gap1;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
"""
def decorate(self, context, obj):
verts, idxs, topo = self.get_path_geom(obj)
self.draw_lines(context, obj, verts, idxs, topo)
self.draw_labels(context, obj)
def draw_labels(self, context, obj):
region = context.region
region3d = context.region_data
dir = Vector((1, 0))
pos = location_3d_to_region_2d(region, region3d, self.get_spline_end(obj))
spline = obj.data.splines[0]
spline_points = spline.bezier_points if spline.bezier_points else spline.points
# generate label text
# same function as in svgwriter.py
def get_label_text():
element = tool.Ifc.get_entity(obj)
B, A = [v.co.xyz for v in spline_points[:2]]
rise = abs(A.z - B.z)
O = A.copy()
O.z = B.z
run = (B - O).length
if run != 0:
angle_tg = rise / run
angle = round(degrees(atan(angle_tg)))
else:
angle = 90
# ues SLOPE_ANGLE as default
if element.ObjectType in ("FALL", "SLOPE_ANGLE"):
return f"{angle}°"
elif element.ObjectType == "SLOPE_FRACTION":
if angle == 90:
return "-"
return f"{self.format_value(context, rise)} / {self.format_value(context, run)}"
elif element.ObjectType == "SLOPE_PERCENT":
if angle == 90:
return "-"
return f"{round(angle_tg * 100)} %"
if spline_points:
text = get_label_text()
self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False)
def get_spline_end(self, obj):
spline = obj.data.splines[0]
spline_points = spline.bezier_points if spline.bezier_points else spline.points
if not spline_points:
return Vector((0, 0, 0))
return obj.matrix_world @ spline_points[0].co
class StairDecorator(BaseDecorator): class StairDecorator(BaseDecorator):
"""Decorating stairs """Decorating stairs
- head point with arrow - head point with arrow
@@ -1444,7 +1571,7 @@ class BattingDecorator(BaseDecorator):
verts, idxs = self.get_editmesh_geom(obj) verts, idxs = self.get_editmesh_geom(obj)
else: else:
verts, idxs = self.get_mesh_geom(obj) verts, idxs = self.get_mesh_geom(obj)
# TODO: find the less ugly way to figure thickness # TODO: find the less ugly way to figure thickness
thickness = DecoratorData.get_batting_thickness(obj) thickness = DecoratorData.get_batting_thickness(obj)
region = context.region region = context.region
@@ -1461,9 +1588,8 @@ class BattingDecorator(BaseDecorator):
obj, obj,
verts[:2], verts[:2],
idxs, idxs,
extra_float_kwargs={ extra_float_kwargs={"batting_thickness_winspace": winspace_thickness},
"batting_thickness_winspace": winspace_thickness}, is_scale_dependant=False,
is_scale_dependant = False
) )
@@ -1924,6 +2050,7 @@ class DecorationsHandler:
ElevationDecorator, ElevationDecorator,
TextDecorator, TextDecorator,
BattingDecorator, BattingDecorator,
FallDecorator,
] ]
installed = None installed = None
@@ -31,7 +31,7 @@ import ifcopenshell.util.representation
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.module.drawing.helper as helper import blenderbim.bim.module.drawing.helper as helper
import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.annotation as annotation
from math import pi, ceil from math import pi, ceil, atan, degrees
from mathutils import Vector from mathutils import Vector
from mathutils import geometry from mathutils import geometry
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
@@ -162,6 +162,8 @@ class SvgWriter:
self.draw_section_level_annotation(obj) self.draw_section_level_annotation(obj)
elif element.ObjectType == "TEXT": elif element.ObjectType == "TEXT":
self.draw_text_annotation(obj, obj.location) self.draw_text_annotation(obj, obj.location)
elif element.ObjectType in ("FALL", "SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.draw_fall_annotations(obj)
else: else:
self.draw_misc_annotation(obj) self.draw_misc_annotation(obj)
@@ -884,12 +886,86 @@ class SvgWriter:
"dominant-baseline": "middle", "dominant-baseline": "middle",
} }
radius = ((matrix_world @ points[-1].co) - (matrix_world @ points[-2].co)).length radius = (points[-1].co - points[-2].co).length
radius = helper.format_distance(radius) radius = helper.format_distance(radius)
tag = element.Description or f"R{radius}" tag = element.Description or f"R{radius}"
self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style)) self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style))
def draw_fall_annotations(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
classes = self.get_attribute_classes(obj)
element = tool.Ifc.get_entity(obj)
matrix_world = obj.matrix_world
for spline in obj.data.splines:
points = self.get_spline_points(spline)
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
d = " ".join(
[
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
for p in projected_points
]
)
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
p0 = Vector(
(
(x_offset + projected_points[0].x) * self.svg_scale,
(y_offset - projected_points[0].y) * self.svg_scale,
)
)
p1 = Vector(
(
(x_offset + projected_points[1].x) * self.svg_scale,
(y_offset - projected_points[1].y) * self.svg_scale,
)
)
# generate label text
# same function as in decoration.py
def get_label_text():
B, A = [v.co.xyz for v in points[:2]]
rise = abs(A.z - B.z)
O = A.copy()
O.z = B.z
run = (B - O).length
if run != 0:
angle_tg = rise / run
angle = round( degrees( atan(angle_tg) ))
else:
angle = 90
# ues SLOPE_ANGLE as default
if element.ObjectType in ("FALL", "SLOPE_ANGLE"):
return f"{angle}°"
elif element.ObjectType == "SLOPE_FRACTION":
if angle == 90:
return "-"
return f"{helper.format_distance(rise)} / {helper.format_distance(run)}"
elif element.ObjectType == "SLOPE_PERCENT":
if angle == 90:
return "-"
return f"{round(angle_tg * 100)} %"
tag = element.Description or get_label_text()
text_offset = (p0 - p1).xy.normalized() * len(tag)
text_position = projected_points[0]
text_position = Vector(
((x_offset + text_position.x) * self.svg_scale, (y_offset - text_position.y) * self.svg_scale)
)
text_position += text_offset
text_style = {
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style))
def draw_diameter_annotations(self, obj): def draw_diameter_annotations(self, obj):
classes = self.get_attribute_classes(obj) classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world matrix_world = obj.matrix_world
@@ -425,6 +425,11 @@ class BIM_PT_annotation_utilities(Panel):
op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE") op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE")
op.object_type = "FILL_AREA" op.object_type = "FILL_AREA"
row = layout.row(align=True)
op = row.operator("bim.add_annotation", text="Fall", icon="SORT_ASC")
op.object_type = "FALL"
op.data_type = "curve"
row = layout.row(align=True) row = layout.row(align=True)
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations") row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
row.enabled = context.scene.camera is not None row.enabled = context.scene.camera is not None
@@ -612,7 +612,6 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
bpy.ops.object.mode_set(mode="EDIT") bpy.ops.object.mode_set(mode="EDIT")
ProfileDecorator.install(context) ProfileDecorator.install(context)
# TODO: test it from properties panel?
if not bpy.app.background: if not bpy.app.background:
bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool") bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool")
return {"FINISHED"} return {"FINISHED"}
@@ -53,6 +53,7 @@ class Drawing(blenderbim.core.tool.Drawing):
"BREAKLINE": "mesh", "BREAKLINE": "mesh",
"DIAMETER": "curve", "DIAMETER": "curve",
"DIMENSION": "curve", "DIMENSION": "curve",
"FALL": "curve",
"FILL_AREA": "mesh", "FILL_AREA": "mesh",
"HIDDEN_LINE": "mesh", "HIDDEN_LINE": "mesh",
"LINEWORK": "mesh", "LINEWORK": "mesh",