diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index adcca4b9cf..73b97ba03f 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -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: diff --git a/src/bonsai/bonsai/bim/module/model/polyline.py b/src/bonsai/bonsai/bim/module/model/polyline.py index 823042ee8b..11c30ee8c2 100644 --- a/src/bonsai/bonsai/bim/module/model/polyline.py +++ b/src/bonsai/bonsai/bim/module/model/polyline.py @@ -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: diff --git a/src/bonsai/bonsai/bim/module/model/product.py b/src/bonsai/bonsai/bim/module/model/product.py index 5987773e51..dbda7109e3 100644 --- a/src/bonsai/bonsai/bim/module/model/product.py +++ b/src/bonsai/bonsai/bim/module/model/product.py @@ -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() diff --git a/src/bonsai/bonsai/bim/module/model/profile.py b/src/bonsai/bonsai/bim/module/model/profile.py index 3a0f1bc587..8ee9da403c 100644 --- a/src/bonsai/bonsai/bim/module/model/profile.py +++ b/src/bonsai/bonsai/bim/module/model/profile.py @@ -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]) diff --git a/src/bonsai/bonsai/bim/module/model/prop.py b/src/bonsai/bonsai/bim/module/model/prop.py index ebe3fa1c8b..0f78e5a317 100644 --- a/src/bonsai/bonsai/bim/module/model/prop.py +++ b/src/bonsai/bonsai/bim/module/model/prop.py @@ -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: diff --git a/src/bonsai/bonsai/bim/module/model/slab.py b/src/bonsai/bonsai/bim/module/model/slab.py index c5fe544e5c..124cf4b876 100644 --- a/src/bonsai/bonsai/bim/module/model/slab.py +++ b/src/bonsai/bonsai/bim/module/model/slab.py @@ -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] diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py index 8f39c4aedf..a55982c4c0 100644 --- a/src/bonsai/bonsai/bim/module/model/wall.py +++ b/src/bonsai/bonsai/bim/module/model/wall.py @@ -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: diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index 9571337ad2..72d86b4196 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -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"} diff --git a/src/bonsai/bonsai/bim/module/project/workspace.py b/src/bonsai/bonsai/bim/module/project/workspace.py index d58ad55adb..e7b9220fc1 100644 --- a/src/bonsai/bonsai/bim/module/project/workspace.py +++ b/src/bonsai/bonsai/bim/module/project/workspace.py @@ -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: diff --git a/src/bonsai/bonsai/bim/module/sequence/operator.py b/src/bonsai/bonsai/bim/module/sequence/operator.py index d85c955180..51ed716bf3 100644 --- a/src/bonsai/bonsai/bim/module/sequence/operator.py +++ b/src/bonsai/bonsai/bim/module/sequence/operator.py @@ -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), ) diff --git a/src/bonsai/bonsai/bim/module/sequence/ui.py b/src/bonsai/bonsai/bim/module/sequence/ui.py index 876ccc5ea2..69a4c81e4f 100644 --- a/src/bonsai/bonsai/bim/module/sequence/ui.py +++ b/src/bonsai/bonsai/bim/module/sequence/ui.py @@ -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( diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index f1c9487cf8..9b51b31581 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -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)): diff --git a/src/bonsai/bonsai/tool/polyline.py b/src/bonsai/bonsai/tool/polyline.py index ec02cd2c3c..e4132c62d2 100644 --- a/src/bonsai/bonsai/tool/polyline.py +++ b/src/bonsai/bonsai/tool/polyline.py @@ -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 diff --git a/src/bonsai/bonsai/tool/project.py b/src/bonsai/bonsai/tool/project.py index 2137e39141..1545c2db87 100644 --- a/src/bonsai/bonsai/tool/project.py +++ b/src/bonsai/bonsai/tool/project.py @@ -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 diff --git a/src/bonsai/bonsai/tool/raycast.py b/src/bonsai/bonsai/tool/raycast.py index 2ed6b0db88..d77c309739 100644 --- a/src/bonsai/bonsai/tool/raycast.py +++ b/src/bonsai/bonsai/tool/raycast.py @@ -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 diff --git a/src/bonsai/bonsai/tool/sequence.py b/src/bonsai/bonsai/tool/sequence.py index 9e9551836e..1f361cf704 100644 --- a/src/bonsai/bonsai/tool/sequence.py +++ b/src/bonsai/bonsai/tool/sequence.py @@ -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: diff --git a/src/bonsai/bonsai/tool/snap.py b/src/bonsai/bonsai/tool/snap.py index 31e1aabaf8..ec03946786 100644 --- a/src/bonsai/bonsai/tool/snap.py +++ b/src/bonsai/bonsai/tool/snap.py @@ -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) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/assign_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/assign_representation.py index 9159ac6acc..06b9f7fd25 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/assign_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/assign_representation.py @@ -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 diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/assign_declaration.py b/src/ifcopenshell-python/ifcopenshell/api/project/assign_declaration.py index d3d164f2bc..2daa7dc729 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/assign_declaration.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/assign_declaration.py @@ -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 diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task.py index 9ad03f7a2f..340709594c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task.py @@ -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 diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/duplicate_task.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/duplicate_task.py index 236ecd89c9..933c6e3c29 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/duplicate_task.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/duplicate_task.py @@ -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 ( diff --git a/src/ifcopenshell-python/ifcopenshell/util/cost.py b/src/ifcopenshell-python/ifcopenshell/util/cost.py index d778603310..804390ed9c 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/cost.py +++ b/src/ifcopenshell-python/ifcopenshell/util/cost.py @@ -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