This commit is contained in:
Andrej730
2025-07-24 19:14:26 +05:00
parent cfd5d2a4eb
commit 05a8824019
22 changed files with 258 additions and 139 deletions
+21 -13
View File
@@ -381,7 +381,7 @@ class PolylineDecorator:
start = self.polyline_points[0]
if len(self.polyline_points) == 1:
end = context.scene.BIMPolylineProperties.snap_mouse_point[0]
end = tool.Model.get_polyline_props().snap_mouse_point[0]
else:
end = self.polyline_points[1]
@@ -632,14 +632,16 @@ class PolylineDecorator:
self.draw_batch("LINES", polyline_verts, self.decorator_color_special, polyline_edges)
def get_polylines_data(self, context):
self.measure_type = context.scene.MeasureToolSettings.measurement_type
self.polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
self.measure_data = context.scene.BIMPolylineProperties.measurement_polyline
self.measure_type = tool.Project.get_measure_tool_settings().measurement_type
polyline_props = tool.Model.get_polyline_props()
self.polyline_data = polyline_props.insertion_polyline
self.measure_data = polyline_props.measurement_polyline
def select_and_draw_measurements_text(self, context):
self.get_polylines_data(context)
if self.polyline_data:
self.polyline_data = context.scene.BIMPolylineProperties.insertion_polyline[0]
polyline_props = tool.Model.get_polyline_props()
self.polyline_data = polyline_props.insertion_polyline[0]
self.polyline_points = self.polyline_data.polyline_points
self.draw_measurements_text(context)
@@ -653,7 +655,8 @@ class PolylineDecorator:
def select_and_draw_measurements_poly(self, context):
self.get_polylines_data(context)
if self.polyline_data:
self.polyline_data = context.scene.BIMPolylineProperties.insertion_polyline[0]
polyline_props = tool.Model.get_polyline_props()
self.polyline_data = polyline_props.insertion_polyline[0]
self.polyline_points = self.polyline_data.polyline_points
self.draw_measurements_poly(context)
@@ -676,11 +679,12 @@ class PolylineDecorator:
region = context.region
rv3d = region.data
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0]
polyline_props = tool.Model.get_polyline_props()
snap_prop = polyline_props.snap_mouse_point[0]
mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z))
try:
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_ref[0]
snap_prop = polyline_props.snap_mouse_ref[0]
mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z))
except:
pass
@@ -742,7 +746,8 @@ class PolylineDecorator:
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
gpu.state.point_size_set(6)
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0]
polyline_props = tool.Model.get_polyline_props()
snap_prop = polyline_props.snap_mouse_point[0]
# Point related to the mouse
mouse_point = [Vector((snap_prop.x, snap_prop.y, snap_prop.z))]
@@ -815,9 +820,10 @@ class PolylineDecorator:
self.draw_batch("LINES", axis2, highlight_color(axis_color2), [(0, 1)])
# Create polyline with selected points
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
if polyline_data:
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline[0]
polyline_data = polyline_props.insertion_polyline[0]
polyline_points = polyline_data.polyline_points
else:
polyline_points = []
@@ -927,7 +933,8 @@ class ProductDecorator:
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
self.line_shader.uniform_float("lineWidth", 2.0)
decorator_color = self.addon_prefs.decorator_color_special
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
self.relating_type = None
@@ -1114,7 +1121,8 @@ class FaceAreaDecorator:
gpu.state.blend_set("ALPHA")
decorator_color = self.addon_prefs.decorator_color_special
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
for i, polyline in enumerate(polyline_data):
vertices = []
for point in polyline.polyline_points:
+15 -8
View File
@@ -92,7 +92,8 @@ def get_wall_preview_data(context, relating_type):
# Verts
polyline_vertices = []
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
if len(polyline_points) < 2:
data = []
@@ -212,7 +213,8 @@ def get_slab_preview_data(context, relating_type):
data["verts"] = []
# Verts
polyline_vertices = []
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
if len(polyline_points) < 3:
data = []
@@ -340,7 +342,8 @@ def get_vertical_profile_preview_data(
tris = [[loop.vert.index for loop in triangles] for triangles in bm.calc_loop_triangles()]
# Calculate rotation, mouse position, angle and cardinal point
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0]
polyline_props = tool.Model.get_polyline_props()
snap_prop = polyline_props.snap_mouse_point[0]
mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z))
data = {}
@@ -384,7 +387,8 @@ def get_horizontal_profile_preview_data(
cardinal_point = model_props.cardinal_point
polyline_verts = []
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
if len(polyline_points) < 2:
return {}
@@ -530,7 +534,8 @@ def get_generic_product_preview_data(context, relating_type):
rl = float(model_props.rl2)
else:
rl = 0
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0]
polyline_props = tool.Model.get_polyline_props()
snap_prop = polyline_props.snap_mouse_point[0]
default_container_elevation = tool.Root.get_default_container_elevation()
mouse_point = Vector((snap_prop.x, snap_prop.y, default_container_elevation))
snap_obj = bpy.data.objects.get(snap_prop.snap_object)
@@ -646,7 +651,8 @@ class PolylineOperator:
def choose_plane(self, event: bpy.types.Event, x: bool = True, y: bool = True, z: bool = True) -> None:
def get_plane_origin():
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
if len(polyline_points) > 0:
reference_point = polyline_points[-1]
@@ -824,7 +830,8 @@ class PolylineOperator:
if event.value == "PRESS" and event.type == "C":
# Get the first point coordinates to close the polyline
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
if len(polyline_points) > 2:
first_point = polyline_points[0]
@@ -832,7 +839,7 @@ class PolylineOperator:
if not (
first_point.x == last_point.x and first_point.y == last_point.y and first_point.z == last_point.z
):
mouse_point = context.scene.BIMPolylineProperties.snap_mouse_point[0]
mouse_point = polyline_props.snap_mouse_point[0]
mouse_point.x = first_point.x
mouse_point.y = first_point.y
if self.input_ui.get_number_value("Z") is not None:
@@ -180,7 +180,8 @@ class DrawOccurrence(bpy.types.Operator, PolylineOperator, tool.Ifc.Operator):
# TODO: when this workflow matures a bit, recode it so it doesn't rely on selection and cursor
# Select snapped object so we can insert doors and windows
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0]
polyline_props = tool.Model.get_polyline_props()
snap_prop = polyline_props.snap_mouse_point[0]
snap_obj = bpy.data.objects.get(snap_prop.snap_object)
if snap_obj:
try:
@@ -193,7 +194,7 @@ class DrawOccurrence(bpy.types.Operator, PolylineOperator, tool.Ifc.Operator):
except:
pass
point = context.scene.BIMPolylineProperties.insertion_polyline[0].polyline_points[0]
point = polyline_props.insertion_polyline[0].polyline_points[0]
context.scene.cursor.location = Vector((point.x, point.y, point.z))
tool.Polyline.clear_polyline()
@@ -545,7 +546,10 @@ class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.change_type_page"
bl_label = "Change Type Page"
bl_options = {"REGISTER"}
page: bpy.props.IntProperty()
page: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
if TYPE_CHECKING:
page: int
def _execute(self, context):
props = tool.Model.get_model_props()
+20 -11
View File
@@ -40,7 +40,10 @@ from math import pi, degrees, atan2
from mathutils import Vector, Matrix
from bonsai.bim.module.model.decorator import ProfileDecorator, PolylineDecorator, ProductDecorator
from bonsai.bim.module.model.polyline import PolylineOperator
from typing import Union, Any, Optional
from typing import Union, Any, Optional, Literal
ProfileFrom2PointsReturn = Union[dict[str, Any], None]
class DumbProfileGenerator:
@@ -48,7 +51,9 @@ class DumbProfileGenerator:
self.relating_type = relating_type
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
def generate(self, insertion_type="CURSOR"):
def generate(
self, insertion_type: Literal["CURSOR", "POLYLINE"] = "CURSOR"
) -> Union[tuple[list[ProfileFrom2PointsReturn], bool], bpy.types.Object, None]:
self.insertion_type = insertion_type
self.file = tool.Ifc.get()
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
@@ -77,8 +82,9 @@ class DumbProfileGenerator:
elif self.insertion_type == "CURSOR":
return self.derive_from_cursor()
def derive_from_polyline(self) -> tuple[list[Union[dict[str, Any], None]], bool]:
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
def derive_from_polyline(self) -> tuple[list[ProfileFrom2PointsReturn], bool]:
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
is_polyline_closed = False
if len(polyline_points) > 3:
@@ -87,7 +93,7 @@ class DumbProfileGenerator:
if first_vec == last_vec:
is_polyline_closed = True
profiles = []
profiles: list[ProfileFrom2PointsReturn] = []
for i in range(len(polyline_points) - 1):
vec1 = Vector((polyline_points[i].x, polyline_points[i].y, polyline_points[i].z))
vec2 = Vector((polyline_points[i + 1].x, polyline_points[i + 1].y, polyline_points[i + 1].z))
@@ -95,11 +101,12 @@ class DumbProfileGenerator:
profiles.append(self.create_profile_from_2_points(coords))
return profiles, is_polyline_closed
def derive_from_cursor(self):
def derive_from_cursor(self) -> bpy.types.Object:
assert bpy.context.scene
self.location = bpy.context.scene.cursor.location
return self.create_profile()
def create_profile(self):
def create_profile(self) -> bpy.types.Object:
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
# Standard cases are deprecated, so let's cull them
ifc_class = next(c for c in ifc_classes if "StandardCase" not in c)
@@ -109,7 +116,7 @@ class DumbProfileGenerator:
matrix_world = Matrix()
if self.relating_type.is_a() not in ("IfcColumnType", "IfcPileType"):
if self.insertion_type not in {"POLYLINE"}:
if self.insertion_type != "POLYLINE":
matrix_world = Matrix.Rotation(pi / 2, 4, "Z") @ Matrix.Rotation(pi / 2, 4, "X") @ matrix_world
matrix_world = Matrix.Rotation(self.rotation, 4, "Z") @ matrix_world
else:
@@ -117,7 +124,7 @@ class DumbProfileGenerator:
matrix_world = rotation_matrix.to_matrix().to_4x4() @ matrix_world
matrix_world.translation = self.location
if self.insertion_type not in {"POLYLINE"} and self.container_obj:
if self.insertion_type != "POLYLINE" and self.container_obj:
matrix_world.translation.z = self.container_obj.location.z
element = bonsai.core.root.assign_class(
tool.Ifc,
@@ -171,12 +178,14 @@ class DumbProfileGenerator:
return obj
def create_profile_from_2_points(self, coords, should_round=False) -> Union[dict[str, Any], None]:
def create_profile_from_2_points(
self, coords: tuple[Vector, Vector], should_round: bool = False
) -> ProfileFrom2PointsReturn:
self.direction = coords[1] - coords[0]
length = self.direction.length
if round(length, 4) < 0.1:
return
data = {"coords": coords}
data: dict[str, Any] = {"coords": coords}
self.depth = length
self.rotation = atan2(self.direction[1], self.direction[0])
@@ -323,6 +323,13 @@ class BIMModelProperties(PropertyGroup):
show_wall_axis: bool
show_slab_direction: bool
prev_transform_orientation_slot_type: str
prev_show_gizmo_object_translate: bool
show_bounding_box: bool
show_cut_decorator: bool
show_cut_decorator_fill: bool
class BIMArrayProperties(PropertyGroup):
is_editing: bpy.props.IntProperty(
@@ -1157,6 +1164,13 @@ class SnapMousePoint(PropertyGroup):
snap_type: bpy.props.StringProperty(name="Snap Type")
snap_object: bpy.props.StringProperty(name="Object Name")
if TYPE_CHECKING:
x: float
y: float
z: float
snap_type: str
snap_object: str
class PolylinePoint(PropertyGroup):
x: bpy.props.FloatProperty(name="X")
@@ -1166,6 +1180,14 @@ class PolylinePoint(PropertyGroup):
angle: bpy.props.StringProperty(name="Angle")
position: bpy.props.FloatVectorProperty(name="Decorator Position", size=3)
if TYPE_CHECKING:
x: float
y: float
z: float
dim: str
angle: str
position: tuple[float, float, float]
class Polyline(PropertyGroup):
id: bpy.props.StringProperty(name="Id")
@@ -1174,6 +1196,13 @@ class Polyline(PropertyGroup):
area: bpy.props.StringProperty(name="Measured Area")
total_length: bpy.props.StringProperty(name="Total Length")
if TYPE_CHECKING:
id: str
polyline_points: bpy.types.bpy_prop_collection_idprop[PolylinePoint]
measurement_type: str
area: str
total_length: str
class BIMPolylineProperties(PropertyGroup):
snap_mouse_point: bpy.props.CollectionProperty(type=SnapMousePoint)
@@ -1181,17 +1210,32 @@ class BIMPolylineProperties(PropertyGroup):
insertion_polyline: bpy.props.CollectionProperty(type=Polyline)
measurement_polyline: bpy.props.CollectionProperty(type=Polyline)
if TYPE_CHECKING:
snap_mouse_point: bpy.types.bpy_prop_collection_idprop[SnapMousePoint]
snap_mouse_ref: bpy.types.bpy_prop_collection_idprop[SnapMousePoint]
insertion_polyline: bpy.types.bpy_prop_collection_idprop[Polyline]
measurement_polyline: bpy.types.bpy_prop_collection_idprop[Polyline]
class ProductPreviewItem(PropertyGroup):
value_3d: bpy.props.FloatVectorProperty()
value_2d: bpy.props.FloatVectorProperty(size=2)
if TYPE_CHECKING:
value_3d: tuple[float, float, float]
value_2d: tuple[float, float]
class BIMProductPreviewProperties(PropertyGroup):
verts: bpy.props.CollectionProperty(type=ProductPreviewItem)
edges: bpy.props.CollectionProperty(type=ProductPreviewItem)
tris: bpy.props.CollectionProperty(type=ProductPreviewItem)
if TYPE_CHECKING:
verts: bpy.types.bpy_prop_collection_idprop[ProductPreviewItem]
edges: bpy.types.bpy_prop_collection_idprop[ProductPreviewItem]
tris: bpy.types.bpy_prop_collection_idprop[ProductPreviewItem]
def update_is_editing(self: "BIMExternalParametricGeometryProperties", context: bpy.types.Context) -> None:
if self.is_editing:
+2 -1
View File
@@ -88,7 +88,8 @@ class DumbSlabGenerator:
return self.derive_from_cursor()
def derive_from_polyline(self):
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
self.location = Vector((polyline_points[0].x, polyline_points[0].y, self.container_obj.location.z))
self.polyline = [tuple(Vector((p.x, p.y, 0.0)) - self.location) for p in polyline_points]
+7 -4
View File
@@ -163,7 +163,8 @@ class ExtendWallsToPolylinePoint(bpy.types.Operator, PolylineOperator, tool.Ifc.
PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0])
tool.Blender.update_viewport()
# Point related to the mouse
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0]
polyline_props = tool.Model.get_polyline_props()
snap_prop = polyline_props.snap_mouse_point[0]
mouse_point = Vector((snap_prop.x, snap_prop.y, snap_prop.z))
angle = atan2(direcion.y, direcion.x)
@@ -214,13 +215,14 @@ class ExtendWallsToPolylinePoint(bpy.types.Operator, PolylineOperator, tool.Ifc.
if result:
self.report({"WARNING"}, result)
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0]
polyline_props = tool.Model.get_polyline_props()
snap_prop = polyline_props.snap_mouse_point[0]
snap_obj = bpy.data.objects.get(snap_prop.snap_object)
if snap_obj and tool.Ifc.get_entity(snap_obj).is_a("IfcWall"):
tool.Blender.set_active_object(snap_obj)
ExtendWallsToWall._execute(self, context)
else:
point = context.scene.BIMPolylineProperties.insertion_polyline[0].polyline_points[1]
point = polyline_props.insertion_polyline[0].polyline_points[1]
core.extend_walls(
tool.Ifc,
tool.Blender,
@@ -867,7 +869,8 @@ class DumbWallGenerator:
return self.derive_from_cursor()
def derive_from_polyline(self) -> tuple[list[Union[dict[str, Any], None]], bool]:
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
is_polyline_closed = False
if len(polyline_points) > 3:
@@ -2625,11 +2625,11 @@ class MeasureTool(bpy.types.Operator, PolylineOperator):
self.handle_snap_selection(context, event)
single_mode = False
polyline_props = tool.Model.get_polyline_props()
if (
self.measure_type == "SINGLE"
and context.scene.BIMPolylineProperties.insertion_polyline
and len(context.scene.BIMPolylineProperties.insertion_polyline[0].polyline_points) >= 2
and polyline_props.insertion_polyline
and len(polyline_props.insertion_polyline[0].polyline_points) >= 2
):
single_mode = True
@@ -2652,19 +2652,19 @@ class MeasureTool(bpy.types.Operator, PolylineOperator):
self.handle_inserting_polyline(context, event)
# Add measurement type to the insertion polyline
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
polyline_data = polyline_props.insertion_polyline
if not polyline_data:
pass
else:
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline[0]
measurement_type = bpy.context.scene.MeasureToolSettings.measurement_type
polyline_data = polyline_props.insertion_polyline[0]
measurement_type = tool.Project.get_measure_tool_settings().measurement_type
if not polyline_data.measurement_type:
polyline_data.measurement_type = measurement_type
tool.Polyline.calculate_area(context, self.input_ui)
if event.type == "E":
context.scene.BIMPolylineProperties.measurement_polyline.clear()
polyline_props.measurement_polyline.clear()
MeasureDecorator.uninstall()
tool.Blender.update_viewport()
@@ -2727,6 +2727,7 @@ class MeasureFaceAreaTool(bpy.types.Operator, PolylineOperator):
return {"PASS_THROUGH"}
self.handle_mouse_move(context, event)
polyline_props = tool.Model.get_polyline_props()
if event.value == "PRESS" and event.type == "LEFTMOUSE":
tool.Blender.update_viewport()
@@ -2737,7 +2738,7 @@ class MeasureFaceAreaTool(bpy.types.Operator, PolylineOperator):
self.clicked_faces.append(obj.data.polygons[face_index])
self.total_area += obj.data.polygons[face_index].area
self.input_ui.set_value("AREA", self.total_area)
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline.add()
polyline_data = polyline_props.insertion_polyline.add()
polyline_data.id = obj.name + str(face_index)
for v_id in obj.data.polygons[face_index].vertices:
vertex = obj.matrix_world @ obj.data.vertices[v_id].co
@@ -2755,14 +2756,14 @@ class MeasureFaceAreaTool(bpy.types.Operator, PolylineOperator):
self.clicked_faces.remove(obj.data.polygons[face_index])
self.total_area -= obj.data.polygons[face_index].area
self.input_ui.set_value("AREA", self.total_area)
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
polyline_data = polyline_props.insertion_polyline
for i, polyline in enumerate(polyline_data):
if polyline.id == obj.name + str(face_index):
polyline_data.remove(i)
tool.Blender.update_viewport()
if event.value == "RELEASE" and event.type in {"ESC", "RIGHTMOUSE"}:
bpy.context.scene.BIMPolylineProperties.insertion_polyline.clear()
polyline_props.insertion_polyline.clear()
context.workspace.status_text_set(text=None)
PolylineDecorator.uninstall()
FaceAreaDecorator.uninstall()
@@ -2786,10 +2787,12 @@ class ClearMeasurement(bpy.types.Operator):
@classmethod
def poll(cls, context):
return len(context.scene.BIMPolylineProperties.measurement_polyline) > 0
polyline_props = tool.Model.get_polyline_props()
return len(polyline_props.measurement_polyline) > 0
def execute(self, context):
context.scene.BIMPolylineProperties.measurement_polyline.clear()
polyline_props = tool.Model.get_polyline_props()
polyline_props.measurement_polyline.clear()
MeasureDecorator.uninstall()
tool.Blender.update_viewport()
return {"FINISHED"}
@@ -59,7 +59,7 @@ class ExploreTool(bpy.types.WorkSpaceTool):
row.label(text="", icon="EVENT_ALT")
row.label(text="Disable Culling" if LinksData.enable_culling else "Enable Culling", icon="EVENT_C")
prop = context.scene.MeasureToolSettings
prop = tool.Project.get_measure_tool_settings()
row = layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="", icon="EVENT_M")
@@ -105,7 +105,7 @@ class ExploreHotkey(bpy.types.Operator):
def hotkey_S_M(self):
for obj in tool.Blender.get_selected_objects():
obj.select_set(False)
measure_type = bpy.context.scene.MeasureToolSettings.measurement_type
measure_type = tool.Project.get_measure_tool_settings().measurement_type
if measure_type == "FACE_AREA":
bpy.ops.bim.measure_face_area_tool("INVOKE_DEFAULT")
else:
@@ -646,10 +646,11 @@ class EditWorkCalendar(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = tool.Sequence.get_work_calendar_props()
core.edit_work_calendar(
tool.Ifc,
tool.Sequence,
work_calendar=tool.Ifc.get().by_id(context.scene.BIMWorkCalendarProperties.active_work_calendar_id),
work_calendar=tool.Ifc.get().by_id(props.active_work_calendar_id),
)
+3 -1
View File
@@ -1021,11 +1021,13 @@ class BIM_PT_work_calendars(Panel):
file = tool.Ifc.get()
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
layout: bpy.types.UILayout
def draw(self, context):
if not SequenceData.is_loaded:
SequenceData.load()
self.props = context.scene.BIMWorkCalendarProperties
self.props = tool.Sequence.get_work_calendar_props()
row = self.layout.row()
if SequenceData.data["has_work_calendars"]:
row.label(
+6
View File
@@ -65,6 +65,7 @@ if TYPE_CHECKING:
BIMStairProperties,
BIMRailingProperties,
BIMExternalParametricGeometryProperties,
BIMPolylineProperties,
)
@@ -101,6 +102,11 @@ class Model(bonsai.core.tool.Model):
def get_epg_props(cls, obj: bpy.types.Object) -> BIMExternalParametricGeometryProperties:
return obj.BIMExternalParametricGeometryProperties
@classmethod
def get_polyline_props(cls) -> BIMPolylineProperties:
assert (scene := bpy.context.scene)
return scene.BIMPolylineProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def convert_si_to_unit(cls, value: T) -> T:
if isinstance(value, (tuple, list)):
+22 -14
View File
@@ -106,7 +106,8 @@ class Polyline(bonsai.core.tool.Polyline):
cls, context: bpy.types.Context, input_ui: PolylineUI, tool_state: ToolState, should_round: bool = False
) -> None:
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
if len(polyline_data) > 0:
polyline_data = polyline_data[0]
polyline_points = polyline_data.polyline_points
@@ -123,7 +124,7 @@ class Polyline(bonsai.core.tool.Polyline):
else:
default_container_elevation = 0
mouse_point = context.scene.BIMPolylineProperties.snap_mouse_point[0]
mouse_point = polyline_props.snap_mouse_point[0]
if last_point_data:
last_point = Vector((last_point_data.x, last_point_data.y, last_point_data.z))
@@ -202,8 +203,9 @@ class Polyline(bonsai.core.tool.Polyline):
@classmethod
def calculate_area(cls, context: bpy.types.Context, input_ui: PolylineUI) -> Union[PolylineUI, None]:
polyline_props = tool.Model.get_polyline_props()
try:
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline[0]
polyline_data = polyline_props.insertion_polyline[0]
polyline_points = polyline_data.polyline_points
except:
return input_ui
@@ -251,9 +253,10 @@ class Polyline(bonsai.core.tool.Polyline):
@classmethod
def calculate_x_y_and_z(cls, context: bpy.types.Context, input_ui: PolylineUI, tool_state: ToolState) -> None:
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
if len(polyline_data) > 0:
polyline_data = context.scene.BIMPolylineProperties.insertion_polyline[0]
polyline_data = polyline_props.insertion_polyline[0]
polyline_points = polyline_data.polyline_points
if len(polyline_points) > 0:
last_point_data = polyline_points[len(polyline_points) - 1]
@@ -269,7 +272,7 @@ class Polyline(bonsai.core.tool.Polyline):
else:
default_container_elevation = 0
snap_prop = context.scene.BIMPolylineProperties.snap_mouse_point[0]
snap_prop = polyline_props.snap_mouse_point[0]
snap_vector = Vector((snap_prop.x, snap_prop.y, snap_prop.z))
if tool_state.is_input_on:
@@ -501,11 +504,13 @@ class Polyline(bonsai.core.tool.Polyline):
d = input_ui.get_formatted_value("D")
a = input_ui.get_formatted_value("A")
snap_vertex = bpy.context.scene.BIMPolylineProperties.snap_mouse_point[0]
polyline_props = tool.Model.get_polyline_props()
snap_vertex = polyline_props.snap_mouse_point[0]
if tool_state and tool_state.use_default_container:
z = tool.Root.get_default_container_elevation()
# Lock one dimension when in plane method
assert tool_state
if tool_state.plane_origin:
if tool_state.plane_method == "XY":
z = tool_state.plane_origin.z
@@ -519,9 +524,9 @@ class Polyline(bonsai.core.tool.Polyline):
y = snap_vertex.y
z = snap_vertex.z
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
polyline_data = polyline_props.insertion_polyline
if not polyline_data:
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline.add()
polyline_data = polyline_props.insertion_polyline.add()
else:
polyline_data = polyline_data[0]
polyline_points = polyline_data.polyline_points
@@ -560,20 +565,23 @@ class Polyline(bonsai.core.tool.Polyline):
@classmethod
def clear_polyline(cls) -> None:
bpy.context.scene.BIMPolylineProperties.insertion_polyline.clear()
polyline_props = tool.Model.get_polyline_props()
polyline_props.insertion_polyline.clear()
@classmethod
def remove_last_polyline_point(cls) -> None:
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
polyline_points.remove(len(polyline_points) - 1)
@classmethod
def move_polyline_to_measure(cls, context: bpy.types.Context, input_ui: PolylineUI) -> None:
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
measurement_data = bpy.context.scene.BIMPolylineProperties.measurement_polyline.add()
measurement_type = bpy.context.scene.MeasureToolSettings.measurement_type
measurement_data = polyline_props.measurement_polyline.add()
measurement_type = tool.Project.get_measure_tool_settings().measurement_type
measurement_data.measurement_type = measurement_type
if measurement_type == "AREA" and len(polyline_points) < 3:
return
+6 -1
View File
@@ -38,7 +38,7 @@ from pathlib import Path
from typing import Optional, Union, TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.project.prop import BIMProjectProperties
from bonsai.bim.module.project.prop import BIMProjectProperties, MeasureToolSettings
HiearchyDict = dict[ifcopenshell.entity_instance, "HiearchyDict"]
@@ -48,6 +48,11 @@ class Project(bonsai.core.tool.Project):
def get_project_props(cls) -> BIMProjectProperties:
return bpy.context.scene.BIMProjectProperties
@classmethod
def get_measure_tool_settings(cls) -> MeasureToolSettings:
assert (scene := bpy.context.scene)
return scene.MeasureToolSettings # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def append_all_types_from_template(cls, template: str) -> None:
# TODO refactor
+2 -1
View File
@@ -316,7 +316,8 @@ class Raycast(bonsai.core.tool.Raycast):
except:
loc = Vector((0, 0, 0))
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline[0]
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline[0]
polyline_points = polyline_data.polyline_points
polyline_points = polyline_points[
: len(polyline_points) - 1
+27 -15
View File
@@ -49,6 +49,7 @@ if TYPE_CHECKING:
BIMAnimationProperties,
BIMStatusProperties,
BIMTaskTreeProperties,
BIMWorkCalendarProperties,
BIMWorkPlanProperties,
BIMWorkScheduleProperties,
)
@@ -83,6 +84,11 @@ class Sequence(bonsai.core.tool.Sequence):
assert (scene := bpy.context.scene)
return scene.BIMWorkPlanProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_work_calendar_props(cls) -> BIMWorkCalendarProperties:
assert (scene := bpy.context.scene)
return scene.BIMWorkCalendarProperties # pyright: ignore[reportAttributeAccessIssue]
@classmethod
def get_work_plan_attributes(cls) -> dict[str, Any]:
import bonsai.bim.module.sequence.helper as helper
@@ -411,7 +417,8 @@ class Sequence(bonsai.core.tool.Sequence):
@classmethod
def get_active_work_time(cls) -> ifcopenshell.entity_instance:
return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkCalendarProperties.active_work_time_id)
props = cls.get_work_calendar_props()
return tool.Ifc.get().by_id(props.active_work_time_id)
@classmethod
def get_task_time(cls, task: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
@@ -571,32 +578,35 @@ class Sequence(bonsai.core.tool.Sequence):
@classmethod
def enable_editing_work_calendar_times(cls, work_calendar: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkCalendarProperties
props = cls.get_work_calendar_props()
props.active_work_calendar_id = work_calendar.id()
props.editing_type = "WORKTIMES"
@classmethod
def load_work_calendar_attributes(cls, work_calendar: ifcopenshell.entity_instance) -> dict[str, Any]:
props = bpy.context.scene.BIMWorkCalendarProperties
props = cls.get_work_calendar_props()
props.work_calendar_attributes.clear()
return bonsai.bim.helper.import_attributes(work_calendar, props.work_calendar_attributes)
@classmethod
def enable_editing_work_calendar(cls, work_calendar: ifcopenshell.entity_instance) -> None:
bpy.context.scene.BIMWorkCalendarProperties.active_work_calendar_id = work_calendar.id()
bpy.context.scene.BIMWorkCalendarProperties.editing_type = "ATTRIBUTES"
props = cls.get_work_calendar_props()
props.active_work_calendar_id = work_calendar.id()
props.editing_type = "ATTRIBUTES"
@classmethod
def disable_editing_work_calendar(cls) -> None:
bpy.context.scene.BIMWorkCalendarProperties.active_work_calendar_id = 0
props = cls.get_work_calendar_props()
props.active_work_calendar_id = 0
@classmethod
def get_work_calendar_attributes(cls) -> dict[str, Any]:
return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMWorkCalendarProperties.work_calendar_attributes)
props = cls.get_work_calendar_props()
return bonsai.bim.helper.export_attributes(props.work_calendar_attributes)
@classmethod
def load_work_time_attributes(cls, work_time: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMWorkCalendarProperties
props = cls.get_work_calendar_props()
props.work_time_attributes.clear()
bonsai.bim.helper.import_attributes(work_time, props.work_time_attributes)
@@ -642,7 +652,7 @@ class Sequence(bonsai.core.tool.Sequence):
for component in recurrence_pattern.MonthComponent or []:
props.month_components[component - 1].is_specified = True
props = bpy.context.scene.BIMWorkCalendarProperties
props = cls.get_work_calendar_props()
initialise_recurrence_components(props)
load_recurrence_pattern_data(work_time, props)
props.active_work_time_id = work_time.id()
@@ -661,12 +671,12 @@ class Sequence(bonsai.core.tool.Sequence):
return True
return False
props = bpy.context.scene.BIMWorkCalendarProperties
props = cls.get_work_calendar_props()
return bonsai.bim.helper.export_attributes(props.work_time_attributes, callback)
@classmethod
def get_recurrence_pattern_attributes(cls, recurrence_pattern):
props = bpy.context.scene.BIMWorkCalendarProperties
props = props = cls.get_work_calendar_props()
attributes = {
"Interval": props.interval if props.interval > 0 else None,
"Occurrences": props.occurrences if props.occurrences > 0 else None,
@@ -693,11 +703,12 @@ class Sequence(bonsai.core.tool.Sequence):
@classmethod
def disable_editing_work_time(cls) -> None:
bpy.context.scene.BIMWorkCalendarProperties.active_work_time_id = 0
props = cls.get_work_calendar_props()
props.active_work_time_id = 0
@classmethod
def get_recurrence_pattern_times(cls) -> Union[tuple[datetime, datetime], None]:
props = bpy.context.scene.BIMWorkCalendarProperties
props = props = cls.get_work_calendar_props()
try:
start_time = parser.parse(props.start_time)
end_time = parser.parse(props.end_time)
@@ -707,8 +718,9 @@ class Sequence(bonsai.core.tool.Sequence):
@classmethod
def reset_time_period(cls) -> None:
bpy.context.scene.BIMWorkCalendarProperties.start_time = ""
bpy.context.scene.BIMWorkCalendarProperties.end_time = ""
props = cls.get_work_calendar_props()
props.start_time = ""
props.end_time = ""
@classmethod
def enable_editing_task_calendar(cls, task: ifcopenshell.entity_instance) -> None:
+17 -10
View File
@@ -111,10 +111,11 @@ class Snap(bonsai.core.tool.Snap):
@classmethod
def update_snapping_point(cls, snap_point, snap_type, snap_obj=None):
polyline_props = tool.Model.get_polyline_props()
try:
snap_vertex = bpy.context.scene.BIMPolylineProperties.snap_mouse_point[0]
snap_vertex = polyline_props.snap_mouse_point[0]
except:
snap_vertex = bpy.context.scene.BIMPolylineProperties.snap_mouse_point.add()
snap_vertex = polyline_props.snap_mouse_point.add()
snap_vertex.x = snap_point[0]
snap_vertex.y = snap_point[1]
@@ -127,14 +128,16 @@ class Snap(bonsai.core.tool.Snap):
@classmethod
def clear_snapping_point(cls):
bpy.context.scene.BIMPolylineProperties.snap_mouse_point.clear()
polyline_props = tool.Model.get_polyline_props()
polyline_props.snap_mouse_point.clear()
@classmethod
def update_snapping_ref(cls, snap_point, snap_type):
polyline_props = tool.Model.get_polyline_props()
try:
snap_vertex = bpy.context.scene.BIMPolylineProperties.snap_mouse_ref[0]
snap_vertex = polyline_props.snap_mouse_ref[0]
except:
snap_vertex = bpy.context.scene.BIMPolylineProperties.snap_mouse_ref.add()
snap_vertex = polyline_props.snap_mouse_ref.add()
snap_vertex.x = snap_point[0]
snap_vertex.y = snap_point[1]
@@ -143,7 +146,8 @@ class Snap(bonsai.core.tool.Snap):
@classmethod
def clear_snapping_ref(cls):
bpy.context.scene.BIMPolylineProperties.snap_mouse_ref.clear()
polyline_props = tool.Model.get_polyline_props()
polyline_props.snap_mouse_ref.clear()
@classmethod
def snap_on_axis(cls, intersection, tool_state):
@@ -167,7 +171,8 @@ class Snap(bonsai.core.tool.Snap):
default_container_elevation = tool.Root.get_default_container_elevation()
else:
default_container_elevation = 0.0
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
if polyline_points:
last_point_data = polyline_points[-1]
@@ -263,7 +268,8 @@ class Snap(bonsai.core.tool.Snap):
intersections.append(tool.Cad.intersect_edge_plane(axis_start, axis_end, snap_point["point"], y_axis))
intersections.append(tool.Cad.intersect_edge_plane(axis_start, axis_end, snap_point["point"], z_axis))
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
polyline_points = polyline_data[0].polyline_points if polyline_data else []
if polyline_points:
last_point_data = polyline_points[-1]
@@ -329,8 +335,9 @@ class Snap(bonsai.core.tool.Snap):
return plane_origin, plane_normal
# Polyline
polyline_props = tool.Model.get_polyline_props()
try:
polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline[0]
polyline_data = polyline_props.insertion_polyline[0]
polyline_points = polyline_data.polyline_points
last_polyline_point = polyline_points[len(polyline_points) - 1]
except:
@@ -344,7 +351,7 @@ class Snap(bonsai.core.tool.Snap):
detected_snaps.append(point)
# Measure
measure_data = context.scene.BIMPolylineProperties.measurement_polyline
measure_data = polyline_props.measurement_polyline
for measure in measure_data:
measure_points = measure.polyline_points
snap_points = tool.Raycast.ray_cast_to_measure(context, event, measure_points)
@@ -27,29 +27,28 @@ def assign_representation(
) -> None:
usecase = Usecase()
usecase.file = file
usecase.settings = {"product": product, "representation": representation}
return usecase.execute()
return usecase.execute(product, representation)
class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self) -> None:
if self.settings["product"].is_a("IfcProduct"):
product_type = ifcopenshell.util.element.get_type(self.settings["product"])
def execute(self, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance) -> None:
if product.is_a("IfcProduct"):
product_type = ifcopenshell.util.element.get_type(product)
if (
product_type
and product_type.RepresentationMaps
and self.settings["representation"].RepresentationType != "MappedRepresentation"
and representation.RepresentationType != "MappedRepresentation"
):
self.settings["product"] = product_type
product = product_type
if self.settings["product"].is_a("IfcProduct"):
self.assign_product_representation(self.settings["product"], self.settings["representation"])
elif self.settings["product"].is_a("IfcTypeProduct"):
if self.settings["product"].RepresentationMaps:
maps = list(self.settings["product"].RepresentationMaps)
if product.is_a("IfcProduct"):
self.assign_product_representation(product, representation)
elif product.is_a("IfcTypeProduct"):
if product.RepresentationMaps:
maps = list(product.RepresentationMaps)
else:
maps = []
self.zero = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
@@ -60,22 +59,22 @@ class Usecase:
"IfcRepresentationMap",
**{
"MappingOrigin": self.file.createIfcAxis2Placement3D(self.zero, self.z_axis, self.x_axis),
"MappedRepresentation": self.settings["representation"],
"MappedRepresentation": representation,
}
)
)
self.settings["product"].RepresentationMaps = maps
product.RepresentationMaps = maps
if self.file.schema == "IFC2X3":
types = self.settings["product"].ObjectTypeOf
types = product.ObjectTypeOf
else:
types = self.settings["product"].Types
types = product.Types
if types:
for element in types[0].RelatedObjects:
mapped_representation = ifcopenshell.api.geometry.map_representation(
self.file, representation=self.settings["representation"]
self.file, representation=representation
)
self.assign_product_representation(element, mapped_representation)
ifcopenshell.api.owner.update_owner_history(self.file, element=self.settings["product"])
ifcopenshell.api.owner.update_owner_history(self.file, element=product)
def assign_product_representation(
self, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
@@ -30,6 +30,8 @@ def assign_declaration(
) -> Union[ifcopenshell.entity_instance, None]:
"""Declares the list of elements to the project
Feature was added in IFC4.
All data in a model must be directly or indirectly related to the
project. Most data is indirectly related, existing instead within the
spatial decomposition tree. Other data, such as types, may be declared
@@ -82,21 +84,16 @@ def assign_declaration(
# All done, just for fun let's save our asset library to disk for later use.
library.write("/path/to/my-library.ifc")
"""
settings = {
"definitions": definitions,
"relating_context": relating_context,
}
relating_context = settings["relating_context"]
all_declares = relating_context.Declares
definitions = set(settings["definitions"])
definitions_set = set(definitions)
previous_declares_rels: set[ifcopenshell.entity_instance] = set()
objects_without_contexts: list[ifcopenshell.entity_instance] = []
objects_with_contexts: list[ifcopenshell.entity_instance] = []
# check if there is anything to change
for definition in definitions:
for definition in definitions_set:
has_context = getattr(definition, "HasContext", None)
if has_context is None:
continue
@@ -20,7 +20,6 @@ import ifcopenshell.api.control
import ifcopenshell.api.root
import ifcopenshell.api.nest
import ifcopenshell
import ifcopenshell.guid
from typing import Optional
@@ -23,14 +23,12 @@ import ifcopenshell.api.owner
import ifcopenshell.api.sequence
import ifcopenshell.util.date
import ifcopenshell.util.element
import ifcopenshell.util.sequence
from typing import Union, Any
# TODO: inconsistent name with other copy_xxx api methods.
def duplicate_task(
file: ifcopenshell.file, task: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
) -> tuple[list[ifcopenshell.entity_instance], list[ifcopenshell.entity_instance]]:
"""Duplicates a task in the project
The following relationships are also duplicated:
@@ -53,24 +51,27 @@ def duplicate_task(
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {"task": task}
return usecase.execute()
return usecase.execute(task)
class Usecase:
file: ifcopenshell.file
settings: dict[str, list[ifcopenshell.entity_instance]]
current: list[ifcopenshell.entity_instance]
duplicate: list[ifcopenshell.entity_instance]
def execute(self):
self.tracker = {"current": [], "duplicate": []}
self.duplicate_task(self.settings["task"])
self.copy_sequence_relationship(self.tracker["current"], self.tracker["duplicate"])
return self.tracker["current"], self.tracker["duplicate"]
def execute(
self, task: ifcopenshell.entity_instance
) -> tuple[list[ifcopenshell.entity_instance], list[ifcopenshell.entity_instance]]:
self.current = []
self.duplicate = []
self.duplicate_task(task)
self.copy_sequence_relationship()
return self.current, self.duplicate
def duplicate_task(self, task: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
new_task = ifcopenshell.util.element.copy_deep(self.file, task)
self.tracker["current"].append(task)
self.tracker["duplicate"].append(new_task)
self.current.append(task)
self.duplicate.append(new_task)
self.copy_indirect_attributes(task, new_task)
return new_task
@@ -118,7 +119,9 @@ class Usecase:
new_value.append(to_element)
inverse[i] = new_value
def copy_sequence_relationship(self, original_tasks, duplicated_tasks):
def copy_sequence_relationship(self) -> None:
original_tasks = self.current
duplicated_tasks = self.duplicate
for i, original_task in enumerate(original_tasks):
for inverse in self.file.get_inverse(original_task):
if inverse.is_a("IfcRelSequence") and (
@@ -202,7 +202,7 @@ def get_root_cost_items(cost_schedule: ifcopenshell.entity_instance) -> list[ifc
def get_all_nested_cost_items(
cost_item: ifcopenshell.entity_instance,
) -> Generator[ifcopenshell.entity_instance, None, None]:
) -> Generator[ifcopenshell.entity_instance]:
for cost_item in get_nested_cost_items(cost_item):
yield cost_item
yield from get_all_nested_cost_items(cost_item)
@@ -217,7 +217,7 @@ def get_nested_cost_items(cost_item: ifcopenshell.entity_instance, is_deep=False
def get_schedule_cost_items(
cost_schedule: ifcopenshell.entity_instance,
) -> Generator[ifcopenshell.entity_instance, None, None]:
) -> Generator[ifcopenshell.entity_instance]:
"""Get all cost schedule cost items, including the nested ones."""
for cost_item in get_root_cost_items(cost_schedule):
yield cost_item