This commit is contained in:
Andrej730
2025-05-14 11:46:20 +05:00
parent 8ce56db0c3
commit 9188dc60ab
2 changed files with 77 additions and 51 deletions
@@ -410,10 +410,15 @@ class CreateDrawing(bpy.types.Operator):
bpy.ops.render.render(write_still=True)
else:
previous_visibility: dict[str, bool] = {}
for obj in tool.Blender.get_object_bim_props(self.camera).collection.objects:
if bpy.context.view_layer.objects.get(obj.name):
collection = tool.Blender.get_object_bim_props(self.camera).collection
assert collection
# Hie annotations.
for obj in collection.objects:
if context.view_layer.objects.get(obj.name):
previous_visibility[obj.name] = obj.hide_get()
obj.hide_set(True)
# Hide objects that shouldn't appear on render - empties, camera, grids, etc.
for obj in context.visible_objects:
if (
(not obj.data and not obj.instance_collection)
@@ -442,15 +447,15 @@ class CreateDrawing(bpy.types.Operator):
return svg_path
def get_linework_contexts(self, ifc: ifcopenshell.file, target_view: str) -> LineworkContexts:
plan_body_target_contexts = []
plan_body_model_contexts = []
model_body_target_contexts = []
model_body_model_contexts = []
plan_body_target_contexts: list[int] = []
plan_body_model_contexts: list[int] = []
model_body_target_contexts: list[int] = []
model_body_model_contexts: list[int] = []
plan_annotation_target_contexts = []
plan_annotation_model_contexts = []
model_annotation_target_contexts = []
model_annotation_model_contexts = []
plan_annotation_target_contexts: list[int] = []
plan_annotation_model_contexts: list[int] = []
model_annotation_target_contexts: list[int] = []
model_annotation_model_contexts: list[int] = []
for rep_context in ifc.by_type("IfcGeometricRepresentationContext"):
if rep_context.is_a("IfcGeometricRepresentationSubContext"):
@@ -522,8 +527,8 @@ class CreateDrawing(bpy.types.Operator):
target_view: str,
) -> None:
drawing_elements = drawing_elements.copy()
contexts: list[list[int]] = getattr(contexts, context_type)
for context in contexts:
contexts_: list[list[int]] = getattr(contexts, context_type)
for context in contexts_:
with profile(f"Processing {context_type} context"):
if not context or not drawing_elements:
continue
@@ -546,7 +551,7 @@ class CreateDrawing(bpy.types.Operator):
tree.add_element(elem)
drawing_elements -= processed
def generate_bisect_linework(self, context: bpy.types.Context, root):
def generate_bisect_linework(self, context: bpy.types.Context, root) -> None:
camera_matrix_i = context.scene.camera.matrix_world.inverted()
group = root.find("{http://www.w3.org/2000/svg}g")
@@ -581,7 +586,7 @@ class CreateDrawing(bpy.types.Operator):
path.attrib["d"] = d
group.append(g)
def generate_wall_layers(self, context: bpy.types.Context, root):
def generate_wall_layers(self, context: bpy.types.Context, root) -> None:
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
if "projection" in el.get("class", "").split():
continue
@@ -22,6 +22,7 @@ import bpy
import math
import bmesh
import shutil
from bpy.types import SplineBezierPoints, SplinePoints
import mathutils
import xml.etree.ElementTree as ET
import svgwrite
@@ -37,7 +38,7 @@ from bonsai.bim.module.drawing.data import DrawingsData
from bonsai.bim.module.drawing.data import DecoratorData
from math import pi, ceil, atan, degrees, acos
from mathutils import geometry, Vector
from typing import Optional, Self
from typing import Optional, Self, Union, Sequence, Callable
from pathlib import Path
@@ -59,6 +60,8 @@ class External(svgwrite.container.Group):
class SvgWriter:
metadata: list[str]
def __init__(self):
self.data_dir = None
self.human_scale = "NTS"
@@ -176,12 +179,20 @@ class SvgWriter:
for child in root:
self.svg.defs.add(External(child))
def draw_annotations(self, annotations, precision, decimal_places):
def draw_annotations(
self,
annotations: list[ifcopenshell.entity_instance],
precision: Union[float, None],
decimal_places: Union[int, None],
) -> Self:
self.precision = precision
self.decimal_places = decimal_places
for element in annotations:
obj = tool.Ifc.get_object(element)
if not obj or (object_type := ifcopenshell.util.element.get_predefined_type(element)) == "DRAWING":
if (
not isinstance(obj, bpy.types.Object)
or (object_type := ifcopenshell.util.element.get_predefined_type(element)) == "DRAWING"
):
continue
elif object_type == "GRID":
self.draw_grid_annotation(obj)
@@ -256,11 +267,12 @@ class SvgWriter:
get_text, tag, dimension_data, text_position=text_position, angle=angle, class_str="SECTIONLEVEL"
)
def draw_stair_annotation(self, obj):
def draw_stair_annotation(self, obj: bpy.types.Object) -> None:
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = obj.matrix_world
classes = self.get_attribute_classes(obj)
assert isinstance(obj.data, bpy.types.Curve)
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]
@@ -284,7 +296,7 @@ class SvgWriter:
)
)
def draw_grid_annotation(self, obj):
def draw_grid_annotation(self, obj: bpy.types.Object) -> None:
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
matrix_world = obj.matrix_world
@@ -358,6 +370,7 @@ class SvgWriter:
def get_attribute_classes(self, obj: bpy.types.Object) -> list[str]:
element = tool.Ifc.get_entity(obj)
assert element
global_id = "GlobalId-{}".format(element.GlobalId)
predefined_type = "PredefinedType-" + tool.Drawing.canonicalise_class_name(
str(ifcopenshell.util.element.get_predefined_type(element))
@@ -374,7 +387,7 @@ class SvgWriter:
)
return classes
def draw_line_annotation(self, obj):
def draw_line_annotation(self, obj: bpy.types.Object) -> None:
# TODO: properly scope these offsets
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -397,7 +410,7 @@ class SvgWriter:
elif isinstance(obj.data, bpy.types.Mesh):
self.draw_edge_annotation(obj, classes)
def draw_edge_annotation(self, obj, classes):
def draw_edge_annotation(self, obj: bpy.types.Object, classes: list[str]) -> None:
predefined_type = classes[2].split("-", 1)[1]
x_offset = self.raw_width / 2
@@ -415,7 +428,7 @@ class SvgWriter:
self.svg.line(start=start * self.svg_scale, end=end * self.svg_scale, class_=" ".join(classes))
)
def draw_batting_annotation():
def draw_batting_annotation() -> None:
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)
@@ -481,41 +494,42 @@ class SvgWriter:
)
self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style))
def draw_revision_cloud_annotation():
def draw_revision_cloud_annotation() -> None:
segment_width = 15.0
base_height = 1
width = 5
def get_svg_half_circle(height, width):
def get_svg_half_circle(height: float, width: float) -> str:
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):
def get_revision_pattern(base_offset: Vector) -> str:
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):
def get_scale(size: float, direction: Vector) -> str:
original_edge = direction * size
current_svg_segments = ceil(size / segment_width) * segment_width * direction
scale = [1 if original_edge[i] == 0 else original_edge[i] / current_svg_segments[i] for i in range(2)]
return "scale(%f, %f)" % (scale[0], scale[1])
def poly_to_edges(poly):
edges = []
def poly_to_edges(poly: Sequence[int]) -> list[tuple[int, int]]:
edges: list[tuple[int, int]] = []
n_verts = len(poly)
lats_index = n_verts - 1
last_index = n_verts - 1
for i in range(len(poly)):
edge = [poly[i], (poly[i + 1]) if i != lats_index else poly[0]]
edge = (poly[i], (poly[i + 1]) if i != last_index else poly[0])
edges.append(edge)
return edges
element = tool.Ifc.get_entity(obj)
assert element
safe_offset_x = 2.0
marker_width = segment_width + safe_offset_x * 2
market_height = 15.0
@@ -573,7 +587,7 @@ class SvgWriter:
)
self.svg.add(polyline)
def draw_section_annotation():
def draw_section_annotation() -> None:
display_data = DecoratorData.get_section_markers_display_data(obj)
connect_markers = display_data["connect_markers"]
@@ -624,7 +638,7 @@ class SvgWriter:
for edge in obj.data.edges:
draw_simple_edge_annotation(*edge.vertices[:])
def draw_leader_annotation(self, obj):
def draw_leader_annotation(self, obj: bpy.types.Object) -> None:
self.draw_line_annotation(obj)
spline = obj.data.splines[0]
spline_points = spline.bezier_points if spline.bezier_points else spline.points
@@ -634,7 +648,7 @@ class SvgWriter:
position = Vector((0, 0, 0))
self.draw_text_annotation(obj, position)
def draw_section_annotation(self, obj):
def draw_section_annotation(self, obj: bpy.types.Object) -> None:
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -688,7 +702,7 @@ class SvgWriter:
)
)
def draw_elevation_annotation(self, obj):
def draw_elevation_annotation(self, obj: bpy.types.Object) -> None:
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
symbol_position = self.project_point_onto_camera(obj.location)
@@ -927,7 +941,7 @@ class SvgWriter:
symbol_transform = self.get_symbol_transform(svg_position_str)
self.draw_symbol(svg_id, symbol_transform)
def draw_break_annotations(self, obj):
def draw_break_annotations(self, obj: bpy.types.Object) -> None:
x_offset = self.raw_width / 2
y_offset = self.raw_height / 2
@@ -950,18 +964,20 @@ class SvgWriter:
d = "M{}".format(d[1:])
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
def draw_plan_level_annotation(self, obj):
def draw_plan_level_annotation(self, obj: bpy.types.Object) -> None:
offset = Vector([self.raw_width, self.raw_height]) / 2
matrix_world = obj.matrix_world
classes = self.get_attribute_classes(obj)
element = tool.Ifc.get_entity(obj)
assert element
description = element.Description
dimension_data = DecoratorData.get_dimension_data(obj)
suppress_zero_inches = dimension_data["suppress_zero_inches"]
base_offset_y = 1.0
assert isinstance(obj.data, bpy.types.Curve)
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]
@@ -1000,7 +1016,8 @@ class SvgWriter:
box_alignment=box_alignment,
)
def draw_angle_annotations(self, obj):
def draw_angle_annotations(self, obj: bpy.types.Object) -> None:
assert isinstance(obj.data, bpy.types.Curve)
points = obj.data.splines[0].points
# [1, 2, 3, 4, 5] -> [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
points_chunked = [points[i : i + 3] for i in range(len(points) - 2)]
@@ -1009,18 +1026,18 @@ class SvgWriter:
points_chunk = [obj.matrix_world @ p.co.xyz for p in points_chunk]
self.draw_svg_3point_arc(obj, points_chunk)
def draw_svg_3point_arc(self, obj, angle_points):
def draw_svg_3point_arc(self, obj: bpy.types.Object, angle_points: list[Vector]) -> None:
"""`angle_points` are expected to be already in world space"""
# This implementation uses an SVG arc, which means that it can only draw
# arcs that are orthogonal to the view (e.g. not arcs in 3D).
# Gosh this is bad code :(
def position_on_svg(p):
def position_on_svg(p: Vector) -> Vector:
p = self.project_point_onto_camera(p)
offset = Vector([self.raw_width / 2, self.raw_height / 2])
return (offset + p.xy * Vector((1, -1))) * self.svg_scale
def get_angle_value():
def get_angle_value() -> float:
"""points should be in world space"""
# calculate arc angle, need to make sure we do it in world space
v0, v1, v2 = angle_points
@@ -1104,10 +1121,11 @@ class SvgWriter:
classes = self.get_attribute_classes(obj)
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
def draw_radius_annotations(self, obj):
def draw_radius_annotations(self, obj: bpy.types.Object) -> None:
offset = Vector([self.raw_width, self.raw_height]) / 2
classes = self.get_attribute_classes(obj)
element = tool.Ifc.get_entity(obj)
assert element
tag = element.Description
matrix_world = obj.matrix_world
@@ -1141,7 +1159,7 @@ class SvgWriter:
get_text, tag, dimension_data, text_position=text_position, class_str="RADIUS", box_alignment="center"
)
def draw_dimension_text(self, get_text, tag, dimension_data, **create_text_kwargs):
def draw_dimension_text(self, get_text: Callable[[], str], tag, dimension_data, **create_text_kwargs) -> None:
prefix = dimension_data["text_prefix"]
suffix = dimension_data["text_suffix"]
show_description_only = dimension_data["show_description_only"]
@@ -1166,12 +1184,13 @@ class SvgWriter:
for text in text_tags:
self.svg.add(text)
def draw_fall_annotations(self, obj):
def draw_fall_annotations(self, obj: bpy.types.Object) -> None:
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
assert isinstance(obj.data, bpy.types.Curve)
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]
@@ -1236,13 +1255,14 @@ class SvgWriter:
text_style = SvgWriter.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):
def draw_diameter_annotations(self, obj: bpy.types.Object) -> None:
classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world
element = tool.Ifc.get_entity(obj)
dimension_text = element.Description
dimension_data = DecoratorData.get_dimension_data(obj)
assert isinstance(obj.data, bpy.types.Curve)
for spline in obj.data.splines:
points = self.get_spline_points(spline)
for i, p in enumerate(points):
@@ -1264,13 +1284,14 @@ class SvgWriter:
custom_unit=dimension_data["custom_unit"],
)
def draw_dimension_annotations(self, obj):
def draw_dimension_annotations(self, obj: bpy.types.Object) -> None:
classes = self.get_attribute_classes(obj)
matrix_world = obj.matrix_world
element = tool.Ifc.get_entity(obj)
dimension_text = element.Description
dimension_data = DecoratorData.get_dimension_data(obj)
assert isinstance(obj.data, bpy.types.Curve)
for spline in obj.data.splines:
points = self.get_spline_points(spline)
for i in range(len(points) - 1):
@@ -1289,7 +1310,7 @@ class SvgWriter:
custom_unit=dimension_data["custom_unit"],
)
def draw_measureit_arch_dimension_annotations(self):
def draw_measureit_arch_dimension_annotations(self) -> None:
try:
import MeasureIt_ARCH.measureit_arch_external_utils
@@ -1303,9 +1324,9 @@ class SvgWriter:
def draw_dimension_annotation(
self,
v0_global,
v1_global,
classes,
v0_global: Vector,
v1_global: Vector,
classes: list[str],
dimension_text=None,
text_format=lambda x: x,
show_description_only=False,
@@ -1314,7 +1335,7 @@ class SvgWriter:
text_suffix="",
fill_bg=False,
custom_unit=None,
):
) -> None:
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)
@@ -1446,7 +1467,7 @@ class SvgWriter:
Vector(self.camera_projection),
)
def get_spline_points(self, spline):
def get_spline_points(self, spline: bpy.types.Spline) -> Union[SplineBezierPoints, SplinePoints]:
return spline.bezier_points if spline.bezier_points else spline.points
def draw_polygon(self, polygon, position):