Section annotation - custom markers / toggle displaying the markers

Now it's possible to set custom markers in section annotation.

To do so you need to add BBIM_Section pset to the annotation and then you can either toggle displaying some marker with ShowStartArrow/ShowEndArrow or you can write the id of some custom symbol in StartArrowSymbol/EndArrowSymbol (need to make sure the symbol is included in symbols.svg).

Other changes:

- removed vertex sorting in decoration.py so that start and end points will be the same thing both in svg and in viewport representation (previously the sorting was needed of the direction the arrow in shader wasn't inverted));

- fixed bug when in svg section arrow symbols wouldn't rotate if you move individual verts of the annotation, not the entire object (I guess this bug sneaked in after adapting elevation annotation)
This commit is contained in:
Andrej730
2023-03-11 11:29:13 +05:00
parent 0dad93bf00
commit fad5c7c921
4 changed files with 217 additions and 139 deletions
@@ -14,9 +14,9 @@ DATA;
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#8=IFCPROPERTYSETTEMPLATE('1Dx2EiZnP67xotInXpwz90',$,'BBIM_Section','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#9,#10,#11,#12,#13));
#9=IFCSIMPLEPROPERTYTEMPLATE('2a_9s8spHDc9dZHtgg71XL',$,'ShowStartArrow','Display start arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('3i6SH_GbT7zhKea$wVE56A',$,'StartArrowSymbol','Custom symbol for the start of the section marker arrow.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#10=IFCSIMPLEPROPERTYTEMPLATE('3i6SH_GbT7zhKea$wVE56A',$,'StartArrowSymbol','Custom symbol for the start of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#11=IFCSIMPLEPROPERTYTEMPLATE('1DtsPn5a9FG8$zXHDDMavY',$,'ShowEndArrow','Display end arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('2$6U0mLI9AiPRWeBabdY3u',$,'EndArrowSymbol','Custom symbol for the end of the section marker arrow',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#12=IFCSIMPLEPROPERTYTEMPLATE('2$6U0mLI9AiPRWeBabdY3u',$,'EndArrowSymbol','Custom symbol for the end of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#13=IFCSIMPLEPROPERTYTEMPLATE('1naFqntIL7igCY7hCaE7kq',$,'HasConnectedSectionLine','Connect or disconnect section markers with line (by default = True).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -126,6 +126,7 @@ class SchedulesData:
class DecoratorData:
# stores 1 type of data per object
data = {}
@classmethod
@@ -142,22 +143,42 @@ class DecoratorData:
return thickness
@classmethod
def get_section_pset_data(cls, obj):
def get_section_markers_display_data(cls, obj):
result = cls.data.get(obj.name, None)
if result is not None:
return result
element = tool.Ifc.get_entity(obj)
if element:
# default values
pset_data = {
"HasConnectedSectionLine": True,
"ShowStartArrow": True,
"StartArrowSymbol": "",
"ShowEndArrow": True,
"EndArrowSymbol": "",
}
obj_pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Section") or {}
pset_data.update(obj_pset_data)
cls.data[obj.name] = pset_data
return pset_data
element = tool.Ifc.get_entity(obj)
if not element:
return
# default values
pset_data = {
"HasConnectedSectionLine": True,
"ShowStartArrow": True,
"StartArrowSymbol": "",
"ShowEndArrow": True,
"EndArrowSymbol": "",
}
obj_pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Section") or {}
pset_data.update(obj_pset_data)
# create more usable display data
start_symbol = pset_data["StartArrowSymbol"]
end_symbol = pset_data["EndArrowSymbol"]
display_data = {
"start": {
"add_circle": pset_data["ShowStartArrow"] and not start_symbol,
"add_symbol": pset_data["ShowStartArrow"] or bool(start_symbol),
"symbol": start_symbol or "section-arrow",
},
"end": {
"add_circle": pset_data["ShowEndArrow"] and not end_symbol,
"add_symbol": pset_data["ShowEndArrow"] or bool(end_symbol),
"symbol": end_symbol or "section-arrow",
},
"connect_markers": pset_data["HasConnectedSectionLine"],
}
cls.data[obj.name] = display_data
return display_data
@@ -1812,6 +1812,10 @@ class SectionDecorator(LevelDecorator):
uniform vec2 winsize;
uniform float viewportDrawingScale;
uniform float connect_markers;
uniform float display_start_symbol;
uniform float display_end_symbol;
uniform float display_start_circle;
uniform float display_end_circle;
layout(lines) in;
layout(line_strip, max_vertices=MAX_POINTS) out;
@@ -1835,7 +1839,7 @@ class SectionDecorator(LevelDecorator):
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
vec4 edge = p1w - p0w, side = normalize(edge);
vec4 gap = side * viewportDrawingScale * CIRCLE_SIZE;
vec4 dir = vec4(cross(vec3(side.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
vec4 edge_ortho_dir = -vec4(cross(vec3(side.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
vec4 p;
vec4 head[CIRCLE_SEGS];
@@ -1844,66 +1848,72 @@ class SectionDecorator(LevelDecorator):
vec4 head5[5];
// start edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p0w + head[i];
if (display_start_circle > 0) {
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p0w + head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p0w + head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
p = p0w + head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// start edge triangle
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p0w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
if (display_start_symbol > 0) {
// start edge triangle
triangle_head(side, edge_ortho_dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p0w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
// end edge circle
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p1w - head[i];
if (display_end_circle > 0) {
for(int i=0; i<CIRCLE_SEGS; i++) {
p = p1w - head[i];
gl_Position = WIN2CLIP(p);
EmitVertex();
}
p = p1w - head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
p = p1w - head[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
// end edge triangle
triangle_head(side, dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p1w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
if (display_end_symbol > 0) {
triangle_head(side, edge_ortho_dir, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, viewportDrawingScale * CIRCLE_SIZE, head5);
p = p1w + head5[0];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[1];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[2];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[3];
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + head5[4];
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
float divider_line_size;
if (connect_markers > 0) {
@@ -1912,30 +1922,39 @@ class SectionDecorator(LevelDecorator):
divider_line_size = CIRCLE_SIZE * 3;
}
// NOTE: basically we consider first vertex to be
// start and the second to be the end marker
// which is a bit inconsistent with svg
// but not that anyone will use multiple edge section annotation
// start reference divider
p = p0w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * divider_line_size;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
if (display_start_symbol > 0) {
p = p0w + side * viewportDrawingScale * divider_line_size;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p0w - side * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
// end reference divider
p = p1w + normalize(vec4(-1, 0, 0, 0)) * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w + normalize(vec4(1, 0, 0, 0)) * viewportDrawingScale * divider_line_size;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
if (display_end_symbol > 0) {
p = p1w + side * viewportDrawingScale * CIRCLE_SIZE;
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w - side * viewportDrawingScale * divider_line_size;
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
}
// stem
if (connect_markers > 0) {
p = p0w + gap;
p = p0w + (display_start_symbol > 0 ? gap : vec4(0));
gl_Position = WIN2CLIP(p);
EmitVertex();
p = p1w - gap;
p = p1w - (display_end_symbol > 0 ? gap : vec4(0));
gl_Position = WIN2CLIP(p);
EmitVertex();
EndPrimitive();
@@ -1948,13 +1967,23 @@ class SectionDecorator(LevelDecorator):
verts, idxs = self.get_editmesh_geom(obj)
else:
verts, idxs = self.get_mesh_geom(obj)
vert_map = {v.freeze(): (obj.matrix_world.inverted() @ v).x for v in verts}
verts = sorted(verts, key=lambda v: vert_map[v], reverse=True)
# TODO: add dashed line to shader
# mind the vertices limit
section_pset_data = DecoratorData.get_section_pset_data(obj)
connect_markers = section_pset_data["HasConnectedSectionLine"]
self.draw_lines(context, obj, verts, idxs, extra_float_kwargs={"connect_markers": float(connect_markers)})
display_data = DecoratorData.get_section_markers_display_data(obj)
self.draw_lines(
context,
obj,
verts,
idxs,
extra_float_kwargs={
"connect_markers": float(display_data["connect_markers"]),
"display_start_symbol": float(display_data["start"]["add_symbol"]),
"display_end_symbol": float(display_data["end"]["add_symbol"]),
"display_start_circle": float(display_data["start"]["add_circle"]),
"display_end_circle": float(display_data["end"]["add_circle"]),
},
)
class TextDecorator(BaseDecorator):
@@ -454,13 +454,17 @@ class SvgWriter:
self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style))
elif predefined_type == "SECTION":
section_pset_data = DecoratorData.get_section_pset_data(obj)
connect_markers = section_pset_data["HasConnectedSectionLine"]
display_data = DecoratorData.get_section_markers_display_data(obj)
connect_markers = display_data["connect_markers"]
if connect_markers:
for edge in obj.data.edges:
draw_simple_edge_annotation(*edge.vertices[:])
else:
for edge in obj.data.edges:
v0_marker_position = "start" if edge.vertices[0] == 0 else "end"
v1_marker_position = "start" if edge.vertices[1] == 0 else "end"
v0_global = matrix_world @ obj.data.vertices[edge.vertices[0]].co.xyz
v1_global = matrix_world @ obj.data.vertices[edge.vertices[1]].co.xyz
v0 = self.project_point_onto_camera(v0_global)
@@ -472,20 +476,23 @@ class SvgWriter:
circle_radius = 5
segment_size = circle_radius * 3
self.svg.add(
self.svg.line(
start=start * self.svg_scale,
end=start * self.svg_scale + edge_dir * segment_size,
class_=" ".join(classes),
if display_data[v0_marker_position]["add_symbol"]:
self.svg.add(
self.svg.line(
start=start * self.svg_scale,
end=start * self.svg_scale + edge_dir * segment_size,
class_=" ".join(classes),
)
)
)
self.svg.add(
self.svg.line(
start=end * self.svg_scale,
end=end * self.svg_scale - edge_dir * segment_size,
class_=" ".join(classes),
if display_data[v1_marker_position]["add_symbol"]:
self.svg.add(
self.svg.line(
start=end * self.svg_scale,
end=end * self.svg_scale - edge_dir * segment_size,
class_=" ".join(classes),
)
)
)
else:
for edge in obj.data.edges:
@@ -507,58 +514,79 @@ class SvgWriter:
self.draw_line_annotation(obj)
v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0)))
v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1)))
angle = -math.degrees((v2 - v1).xy.angle_signed(Vector((0, 1))))
display_data = DecoratorData.get_section_markers_display_data(obj)
for vert in obj.data.vertices:
point = obj.matrix_world @ vert.co
symbol_position = self.project_point_onto_camera(point)
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
transform = "rotate({}, {}, {})".format(
angle,
(symbol_position * self.svg_scale)[0],
(symbol_position * self.svg_scale)[1],
)
for edge in obj.data.edges:
edge_verts = [obj.data.vertices[v_i] for v_i in edge.vertices]
self.svg.add(
self.svg.use("#section-arrow", insert=tuple(symbol_position * self.svg_scale), transform=transform)
)
self.svg.add(self.svg.use("#section-tag", insert=tuple(symbol_position * self.svg_scale)))
# convert edge vertiices coordinates to svg space
# to calculate the edge angle and for later use
edge_verts_svg = []
for vert in edge_verts:
point = obj.matrix_world @ vert.co
symbol_position = self.project_point_onto_camera(point)
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
symbol_position_svg = symbol_position * self.svg_scale
edge_verts_svg.append(symbol_position_svg)
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
text_position = list(symbol_position * self.svg_scale)
text_style = {
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
self.svg.add(self.svg.text(reference_id, insert=(text_position[0], text_position[1] - 2.5), class_="SECTION", **text_style))
self.svg.add(self.svg.text(sheet_id, insert=(text_position[0], text_position[1] + 2.5), class_="SECTION", **text_style))
edge_dir = (edge_verts_svg[1] - edge_verts_svg[0]).normalized()
angle = degrees( edge_dir.xy.angle_signed( Vector([1,0]) ) )
for v_i, symbol_position_svg in zip(edge.vertices, edge_verts_svg):
current_marker_position = "start" if v_i == 0 else "end"
# marker arrow symbol
if display_data[current_marker_position]["add_symbol"]:
transform = "rotate({}, {}, {})".format(angle, *symbol_position_svg.xy)
symbol_id = display_data[current_marker_position]["symbol"]
self.svg.add(self.svg.use(f"#{symbol_id}", insert=symbol_position_svg, transform=transform))
# marker circle and it's text
if display_data[current_marker_position]["add_circle"]:
self.svg.add(self.svg.use("#section-tag", insert=symbol_position_svg))
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
text_position = symbol_position_svg
text_style = {
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
self.svg.add(
self.svg.text(
reference_id,
insert=(text_position[0], text_position[1] - 2.5),
class_="SECTION",
**text_style,
)
)
self.svg.add(
self.svg.text(
sheet_id,
insert=(text_position[0], text_position[1] + 2.5),
class_="SECTION",
**text_style
)
)
def draw_elevation_annotation(self, obj):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
symbol_position = self.project_point_onto_camera(obj.location)
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
symbol_position_svg = symbol_position * self.svg_scale
v1 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, 0)))
v2 = self.project_point_onto_camera(obj.matrix_world @ Vector((0, 0, -1)))
angle = -math.degrees((v2 - v1).xy.angle_signed(Vector((0, 1))))
transform = "rotate({}, {}, {})".format(
angle,
(symbol_position * self.svg_scale)[0],
(symbol_position * self.svg_scale)[1],
)
transform = "rotate({}, {}, {})".format(angle, *symbol_position_svg.xy)
self.svg.add(
self.svg.use("#elevation-arrow", insert=tuple(symbol_position * self.svg_scale), transform=transform)
)
self.svg.add(self.svg.use("#elevation-tag", insert=tuple(symbol_position * self.svg_scale)))
self.svg.add(self.svg.use("#elevation-arrow", insert=symbol_position_svg, transform=transform))
self.svg.add(self.svg.use("#elevation-tag", insert=symbol_position_svg))
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
text_position = list(symbol_position * self.svg_scale)
text_position = symbol_position_svg
text_style = {
"text-anchor": "middle",
"alignment-baseline": "middle",
@@ -766,7 +794,7 @@ class SvgWriter:
bm.verts.index_update()
bm.edges.index_update()
new_verts = [bm.verts.new(p) for p in points_chunk]
new_edges = [bm.edges.new( (new_verts[e[0]], new_verts[e[1]]) ) for e in ((0, 1), (1, 2))]
new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in ((0, 1), (1, 2))]
self.draw_svg_3point_arc(obj, bm)
def draw_svg_3point_arc(self, obj, bm):
@@ -967,7 +995,7 @@ class SvgWriter:
run = (B - O).length
if run != 0:
angle_tg = rise / run
angle = round( degrees( atan(angle_tg) ))
angle = round(degrees(atan(angle_tg)))
else:
angle = 90