Dimension description in viewport/option to hide the measurements #2918

same thing for diameter annotations since they are just reusing dimension annotations

+ some refactor along the way
This commit is contained in:
Andrej730
2023-04-14 16:04:05 +05:00
parent d9726efee0
commit f6a95e8c17
5 changed files with 134 additions and 136 deletions
@@ -27,5 +27,7 @@ DATA;
#20=IFCSIMPLEPROPERTYTEMPLATE('23tejOrxj859R_eCRp1AFP',$,'MarkersPath','Default markers SVG',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
#21=IFCSIMPLEPROPERTYTEMPLATE('1UDakJ5_f7kBhggNSW4$h5',$,'SymbolsPath','Default symbols SVG',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
#22=IFCSIMPLEPROPERTYTEMPLATE('0d53LEtgLDQxnv__NfgH7i',$,'PatternsPath','Default patterns SVG',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
#23=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#24));
#24=IFCSIMPLEPROPERTYTEMPLATE('1rL2AbQsXD8RbpoWH5pYOV',$,'ShowDescriptionOnly','Hide the measurement values and show only annotation description',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
@@ -259,15 +259,16 @@ class DecoratorData:
cls.data[obj.name] = text_data
return text_data
# used by Ifc Annotations with ObjectType = "DIMENSION"
# used by Ifc Annotations with ObjectType = "DIMENSION" / "DIAMETER"
@classmethod
def get_dimension_style(cls, obj):
def get_dimension_data(cls, obj):
result = cls.data.get(obj.name, None)
if result is not None:
return result
element = tool.Ifc.get_entity(obj)
if not element or not element.is_a("IfcAnnotation") or element.ObjectType != "DIMENSION":
supported_object_types = ("DIMENSION", "DIAMETER")
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in supported_object_types:
return None
dimension_style = "arrow"
@@ -275,8 +276,13 @@ class DecoratorData:
if classes and "oblique" in classes.lower().split():
dimension_style = "oblique"
cls.data[obj.name] = dimension_style
return dimension_style
show_description_only = (
ifcopenshell.util.element.get_pset(element, "BBIM_Dimension", "ShowDescriptionOnly") or False
)
dimension_data = {"dimension_style": dimension_style, "show_description_only": show_description_only}
cls.data[obj.name] = dimension_data
return dimension_data
class AnnotationData:
@@ -523,7 +523,7 @@ class DimensionDecorator(BaseDecorator):
def decorate(self, context, obj):
verts, idxs, _ = self.get_path_geom(obj, topo=False)
dimension_style = DecoratorData.get_dimension_style(obj)
dimension_style = DecoratorData.get_dimension_data(obj)["dimension_style"]
self.draw_lines(
context, obj, verts, idxs, extra_float_kwargs={"use_oblique_style": float(dimension_style == "oblique")}
)
@@ -532,17 +532,30 @@ class DimensionDecorator(BaseDecorator):
def draw_labels(self, context, obj, vertices, indices):
region = context.region
region3d = context.region_data
element = tool.Ifc.get_entity(obj)
description = element.Description
show_description_only = DecoratorData.get_dimension_data(obj)["show_description_only"]
for i0, i1 in indices:
v0 = Vector(vertices[i0])
v1 = Vector(vertices[i1])
p0 = location_3d_to_region_2d(region, region3d, v0)
p1 = location_3d_to_region_2d(region, region3d, v1)
dir = p1 - p0
if dir.length < 1:
text_dir = p1 - p0
if text_dir.length < 1:
continue
length = (v1 - v0).length
text = self.format_value(context, length)
self.draw_label(context, text, p0 + (dir) * 0.5, dir)
if not show_description_only:
length = (v1 - v0).length
text = self.format_value(context, length)
self.draw_label(context, text, p0 + text_dir * 0.5, text_dir, box_alignment="bottom-middle")
if description:
self.draw_label(context, description, p0 + text_dir * 0.5, text_dir, box_alignment="top-middle")
elif show_description_only and description:
self.draw_label(context, description, p0 + text_dir * 0.5, text_dir, box_alignment="bottom-middle")
class AngleDecorator(BaseDecorator):
@@ -454,11 +454,12 @@ class BIMAssignedProductProperties(PropertyGroup):
# ObjectType: annotation_name, description, icon, data_type
# fmt: off
ANNOTATION_TYPES_DATA = {
"DIMENSION": ("Dimension", "", "FIXED_SIZE", "curve"),
"DIMENSION": ("Dimension", "Add dimensions annotation.\nMeasurement values can be hidden through ShowDescriptionOnly property\nof BBIM_Dimension property set", "FIXED_SIZE", "curve"),
"ANGLE": ("Angle", "", "DRIVER_ROTATIONAL_DIFFERENCE", "curve"),
"RADIUS": ("Radius", "", "FORWARD", "curve"),
"DIAMETER": ("Diameter", "", "ARROW_LEFTRIGHT", "curve"),
"DIAMETER": ("Diameter", "Add diameter annotation.\nMeasurement values can be hidden through ShowDescriptionOnly property\nof BBIM_Dimension property set", "ARROW_LEFTRIGHT", "curve"),
"TEXT": ("Text", "", "SMALL_CAPS", "empty"),
"TEXT_LEADER": ("Leader", "", "TRACKING_BACKWARDS", "curve"),
"STAIR_ARROW": ("Stair Arrow", "Add stair arrow annotation.\nIf you have IfcStairFlight object selected, it will be used as a reference for the annotation", "SCREEN_BACK", "curve"),
@@ -471,6 +472,7 @@ ANNOTATION_TYPES_DATA = {
"FILL_AREA": ("Fill Area", "", "NODE_TEXTURE", "mesh"),
"FALL": ("Fall", "", "SORT_ASC", "curve"),
}
# fmt: on
annotation_classes = [(x, *ANNOTATION_TYPES_DATA[x][:3], i) for i, x in enumerate(ANNOTATION_TYPES_DATA)]
@@ -233,11 +233,7 @@ class SvgWriter:
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
rl *= unit_scale
rl = "{:.3f}m".format(rl)
text_style = {
"text-anchor": "start",
"alignment-baseline": "baseline",
"dominant-baseline": "baseline",
}
text_style = self.get_box_alignment_parameters("bottom-left")
self.svg.add(self.svg.text(f"RL +{rl}", insert=tuple(text_position), class_="SECTIONLEVEL", **text_style))
if tag:
self.svg.add(self.svg.text(tag, insert=(text_position[0], text_position[1] - 5), **text_style))
@@ -266,11 +262,7 @@ class SvgWriter:
"UP",
insert=tuple(text_position),
class_="STAIR",
**{
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
},
**self.get_box_alignment_parameters("center"),
)
)
@@ -294,16 +286,13 @@ class SvgWriter:
)
line["stroke-dasharray"] = "12.5, 3, 3, 3"
axis_tag = tool.Ifc.get_entity(obj).Name
text_style = self.get_box_alignment_parameters("center")
self.svg.add(
self.svg.text(
axis_tag,
insert=tuple(start * self.svg_scale),
class_="GRID",
**{
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
},
**text_style,
)
)
self.svg.add(
@@ -311,11 +300,7 @@ class SvgWriter:
axis_tag,
insert=tuple(end * self.svg_scale),
class_="GRID",
**{
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
},
**text_style,
)
)
@@ -571,11 +556,7 @@ class SvgWriter:
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",
}
text_style = self.get_box_alignment_parameters("center")
self.svg.add(
self.svg.text(
reference_id,
@@ -608,11 +589,7 @@ class SvgWriter:
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",
}
text_style = self.get_box_alignment_parameters("center")
self.svg.add(
self.svg.text(
reference_id, insert=(text_position[0], text_position[1] - 2.5), class_="ELEVATION", **text_style
@@ -639,6 +616,43 @@ class SvgWriter:
return (reference_id, sheet_id)
return ("-", "-")
def get_box_alignment_parameters(self, box_alignment):
"""Convenience method to get svg parameters for text alignment
in a readable way.
Metehod expecting values like:
`top-left`, `top-middle`, `top-right`,
`middle-left`, `center`, `middle-right`,
`bottom-left`, `bottom-middle`, `bottom-right`
"""
# reference for alignment values:
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline
vertical_alignment = {
"top": "hanging",
"bottom": "baseline",
"center": "middle",
"middle": "middle",
}
alignment_baseline = vertical_alignment[next(align for align in vertical_alignment if align in box_alignment)]
horizontal_alignment = {
"left": "start",
"right": "end",
"center": "middle",
"middle": "middle",
}
text_anchor = horizontal_alignment[next(align for align in horizontal_alignment if align in box_alignment)]
# using dominant-baseline because we plan to use <tspan> subtags
# otherwise alignment-baseline would be sufficient
return {
"dominant-baseline": alignment_baseline,
"text-anchor": text_anchor,
}
def draw_text_annotation(self, text_obj, position):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -684,34 +698,6 @@ class SvgWriter:
if not symbol_svg or not template_text_fields:
self.svg.add(self.svg.use(f"#{symbol}", insert=text_position_svg))
def get_box_alignment_parameters(box_alignment):
# reference for alignment values:
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/alignment-baseline
vertical_alignment = {
"top": "hanging",
"bottom": "baseline",
"center": "middle",
"middle": "middle",
}
alignment_baseline = vertical_alignment[
next(align for align in vertical_alignment if align in box_alignment)
]
horizontal_alignment = {
"left": "start",
"right": "end",
"center": "middle",
"middle": "middle",
}
text_anchor = horizontal_alignment[next(align for align in horizontal_alignment if align in box_alignment)]
# using dominant-baseline because we plan to use <tspan> subtags
# otherwise alignment-baseline would be sufficient
return {
"dominant-baseline": alignment_baseline,
"text-anchor": text_anchor,
}
for text_literal in text_literals:
# after pretty indentation some redundant spaces can occur in svg tags
# this is why we apply "font-size: 0;" to the text tag to remove those spaces
@@ -725,7 +711,7 @@ class SvgWriter:
"transform": transform,
"style": "font-size: 0;",
},
**get_box_alignment_parameters(text_literal.BoxAlignment),
**self.get_box_alignment_parameters(text_literal.BoxAlignment),
)
self.svg.add(text_tag)
@@ -803,20 +789,15 @@ class SvgWriter:
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
rl *= unit_scale
rl = "{:.3f}m".format(rl)
if projected_points[0].x > projected_points[-1].x:
text_anchor = "end"
else:
text_anchor = "start"
box_alignment = "bottom-left" if projected_points[0].x <= projected_points[-1].x else "bottom-right"
text_style = self.get_box_alignment_parameters(box_alignment)
self.svg.add(
self.svg.text(
"RL +{}".format(rl),
insert=tuple(text_position),
class_="PLANLEVEL",
**{
"text-anchor": text_anchor,
"alignment-baseline": "baseline",
"dominant-baseline": "baseline",
},
**text_style,
)
)
@@ -932,12 +913,7 @@ class SvgWriter:
text_offset = (text_position - center_position).xy.normalized() * 5
text_position += text_offset
text_style = {
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
text_style = self.get_box_alignment_parameters("center")
angle_text = abs(round(math.degrees(angle), 3))
if is_reflex:
angle_text = 360 - angle_text
@@ -998,12 +974,7 @@ class SvgWriter:
)
text_position += text_offset
text_style = {
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
text_style = self.get_box_alignment_parameters("center")
radius = (points[-1].co - points[-2].co).length
radius = helper.format_distance(radius, precision=self.precision)
tag = element.Description or f"R{radius}"
@@ -1076,19 +1047,15 @@ class SvgWriter:
)
text_position += text_offset
text_style = {
"text-anchor": "middle",
"alignment-baseline": "middle",
"dominant-baseline": "middle",
}
text_style = self.get_box_alignment_parameters("center")
self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style))
def draw_diameter_annotations(self, obj):
classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world
element = tool.Ifc.get_entity(obj)
text_override = element.Description
dimension_text = element.Description
show_description_only = DecoratorData.get_dimension_data(obj)["show_description_only"]
for spline in obj.data.splines:
points = self.get_spline_points(spline)
for i, p in enumerate(points):
@@ -1097,22 +1064,32 @@ class SvgWriter:
v0_global = matrix_world @ points[i].co.xyz
v1_global = matrix_world @ points[i + 1].co.xyz
self.draw_dimension_annotation(
v0_global, v1_global, classes, text_override, text_format=lambda x: "D" + x
v0_global,
v1_global,
classes,
dimension_text,
text_format=lambda x: "D" + x,
show_description_only=show_description_only,
)
def draw_dimension_annotations(self, obj):
classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world
element = tool.Ifc.get_entity(obj)
text_override = element.Description
dimension_text = element.Description
show_description_only = DecoratorData.get_dimension_data(obj)["show_description_only"]
for spline in obj.data.splines:
points = self.get_spline_points(spline)
for i, p in enumerate(points):
if i + 1 >= len(points):
continue
for i in range(len(points) - 1):
v0_global = matrix_world @ points[i].co.xyz
v1_global = matrix_world @ points[i + 1].co.xyz
self.draw_dimension_annotation(v0_global, v1_global, classes, text_override)
self.draw_dimension_annotation(
v0_global,
v1_global,
classes,
dimension_text=dimension_text,
show_description_only=show_description_only,
)
def draw_measureit_arch_dimension_annotations(self):
try:
@@ -1126,46 +1103,44 @@ class SvgWriter:
Vector(coord[0]), Vector(coord[1]), ["IfcAnnotation", "PredefinedType-DIMENSION"]
)
def draw_dimension_annotation(self, v0_global, v1_global, classes, text_override=None, text_format=lambda x: x):
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
def draw_dimension_annotation(
self, v0_global, v1_global, classes, dimension_text=None, text_format=lambda x: x, show_description_only=False
):
offset = Vector([self.raw_width, self.raw_height]) / 2
v0 = self.project_point_onto_camera(v0_global)
v1 = self.project_point_onto_camera(v1_global)
start = Vector(((x_offset + v0.x), (y_offset - v0.y)))
end = Vector(((x_offset + v1.x), (y_offset - v1.y)))
start = (offset + v0.xy * Vector((1, -1))) * self.svg_scale
end = (offset + v1.xy * Vector((1, -1))) * self.svg_scale
mid = ((end - start) / 2) + start
vector = end - start
perpendicular = Vector((vector.y, -vector.x)).normalized()
dimension = (v1_global - v0_global).length
dimension = helper.format_distance(dimension, precision=self.precision)
sheet_dimension = ((end * self.svg_scale) - (start * self.svg_scale)).length
if sheet_dimension < 5: # annotation can't fit
# offset text to right of marker
text_position = (end * self.svg_scale) + perpendicular + (3 * vector.normalized())
else:
text_position = (mid * self.svg_scale) + perpendicular
sheet_dimension = (end - start).length
# if annotation can't fit offset text to the right of marker
text_position = mid if sheet_dimension > 5 else (end + (3 * vector.normalized()))
rotation = math.degrees(vector.angle_signed(Vector((1, 0))))
line = self.svg.add(
self.svg.line(
start=tuple(start * self.svg_scale), end=tuple(end * self.svg_scale), class_=" ".join(classes)
)
)
if text_override is not None:
text = text_override
else:
text = str(dimension)
text = text_format(text)
self.svg.add(
self.svg.text(
text,
insert=tuple(text_position),
line = self.svg.line(start=start, end=end, class_=" ".join(classes))
self.svg.add(line)
def create_text_tag(text, text_position, box_alignment):
text_kwargs = {"transform": "rotate({} {} {})".format(rotation, text_position.x, text_position.y)}
return self.svg.text(
text_format(text),
insert=text_position,
class_="DIMENSION",
**{
"transform": "rotate({} {} {})".format(rotation, text_position.x, text_position.y),
"text-anchor": "middle",
},
**(text_kwargs | self.get_box_alignment_parameters(box_alignment)),
)
)
if not show_description_only:
self.svg.add(create_text_tag(str(dimension), text_position + perpendicular, "bottom-middle"))
if dimension_text:
self.svg.add(create_text_tag(dimension_text, text_position - perpendicular, "top-middle"))
elif show_description_only and dimension_text:
self.svg.add(create_text_tag(dimension_text, text_position + perpendicular, "bottom-middle"))
def project_point_onto_camera(self, point):
# TODO is this needlessly complex?