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