diff --git a/src/bonsai/bonsai/bim/module/context/prop.py b/src/bonsai/bonsai/bim/module/context/prop.py index 783c426e87..3a6f3c226f 100644 --- a/src/bonsai/bonsai/bim/module/context/prop.py +++ b/src/bonsai/bonsai/bim/module/context/prop.py @@ -30,6 +30,7 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) +from typing import TYPE_CHECKING class BIMContextProperties(PropertyGroup): @@ -66,3 +67,10 @@ class BIMContextProperties(PropertyGroup): ) active_context_id: IntProperty(name="Active Context Id") context_attributes: CollectionProperty(name="Context Attributes", type=Attribute) + + if TYPE_CHECKING: + contexts: str + subcontexts: str + target_views: str + active_context_id: int + context_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] diff --git a/src/bonsai/bonsai/bim/module/context/ui.py b/src/bonsai/bonsai/bim/module/context/ui.py index 2cfe5cf3f1..8cc3cf4799 100644 --- a/src/bonsai/bonsai/bim/module/context/ui.py +++ b/src/bonsai/bonsai/bim/module/context/ui.py @@ -33,13 +33,14 @@ class BIM_PT_context(bpy.types.Panel): @classmethod def poll(cls, context): - return tool.Ifc.get() + return bool(tool.Ifc.get()) def draw(self, context): if not ContextData.is_loaded: ContextData.load() - props = context.scene.BIMContextProperties + assert self.layout + props = tool.Context.get_context_props() row = self.layout.row(align=True) row.prop(props, "contexts", text="") diff --git a/src/bonsai/bonsai/bim/module/cost/prop.py b/src/bonsai/bonsai/bim/module/cost/prop.py index 4c5e0d9529..fa57cfb0a8 100644 --- a/src/bonsai/bonsai/bim/module/cost/prop.py +++ b/src/bonsai/bonsai/bim/module/cost/prop.py @@ -90,7 +90,7 @@ def update_cost_item_identification(self: "CostItem", context: bpy.types.Context **{"cost_item": self.file.by_id(self.ifc_definition_id), "attributes": {"Identification": self.identification}}, ) if props.active_cost_item_id == self.ifc_definition_id: - attribute = props.cost_item_attributes.get("Identification") + attribute = props.cost_item_attributes["Identification"] attribute.string_value = self.identification @@ -105,7 +105,7 @@ def update_cost_item_name(self: "CostItem", context: bpy.types.Context) -> None: **{"cost_item": self.file.by_id(self.ifc_definition_id), "attributes": {"Name": self.name}}, ) if props.active_cost_item_id == self.ifc_definition_id: - attribute = props.cost_item_attributes.get("Name") + attribute = props.cost_item_attributes["Name"] attribute.string_value = self.name diff --git a/src/bonsai/bonsai/bim/module/drawing/annotation.py b/src/bonsai/bonsai/bim/module/drawing/annotation.py index 23d243671d..d41c901e4d 100644 --- a/src/bonsai/bonsai/bim/module/drawing/annotation.py +++ b/src/bonsai/bonsai/bim/module/drawing/annotation.py @@ -129,7 +129,9 @@ class Annotator: def get_annotation_obj( drawing: ifcopenshell.entity_instance, object_type: str, data_type: tool.Drawing.ANNOTATION_DATA_TYPE ) -> bpy.types.Object: + assert bpy.context.scene camera = tool.Ifc.get_object(drawing) + assert isinstance(camera, bpy.types.Object) # those annotations you want to obey the depth of the 3d cursor if object_type == "PLAN_LEVEL": co1 = bpy.context.scene.cursor.location.copy() diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index 208c1d77a9..2cd339c859 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -1619,8 +1619,9 @@ class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): props = tool.Drawing.get_annotation_props() - if not (drawing := tool.Ifc.get_entity(context.scene.camera)): - self.report({"WARNING"}, "Not a BIM camera") + dprops = tool.Drawing.get_document_props() + if not (drawing := dprops.get_active_drawing()): + self.report({"WARNING"}, "No active drawing.") return obj = core.add_annotation( @@ -2643,6 +2644,7 @@ class ActivateDrawingStyle(bpy.types.Operator, tool.Ifc.Operator): def parse_filter_query(self, mode: Literal["INCLUDE", "EXCLUDE"], context: bpy.types.Context) -> None: if mode == "INCLUDE": + assert context.scene objects = context.scene.objects elif mode == "EXCLUDE": objects = context.visible_objects @@ -3103,6 +3105,7 @@ class OrderTextLiteralDown(bpy.types.Operator): return {"FINISHED"} +# Ifc Operator is unnecessary, because suboperator is handling IFC changes. class AssignSelectedObjectAsProduct(bpy.types.Operator): bl_idname = "bim.assign_selected_as_product" bl_label = "Assign Selected Object As Product" @@ -3116,18 +3119,25 @@ class AssignSelectedObjectAsProduct(bpy.types.Operator): return True def execute(self, context): + assert bpy.context.view_layer objs = context.selected_objects[:] - obj1 = objs[0] + obj1, obj2 = objs element1 = tool.Ifc.get_entity(obj1) - obj2 = objs[1] element2 = tool.Ifc.get_entity(obj2) + assert element1 and element2 if element1.is_a("IfcAnnotation"): other_selected_object = obj2 bpy.context.view_layer.objects.active = obj1 elif element2.is_a("IfcAnnotation"): other_selected_object = obj1 bpy.context.view_layer.objects.active = obj2 - context.active_object.BIMAssignedProductProperties.relating_product = other_selected_object + else: + self.report({"ERROR"}, "One of the selected objects must be IfcAnnotation.") + return {"CANCELLED"} + + assert (active_obj := context.active_object) + props = tool.Drawing.get_object_assigned_product_props(active_obj) + props.relating_product = other_selected_object bpy.ops.bim.edit_assigned_product() return {"FINISHED"} @@ -3139,9 +3149,11 @@ class EditAssignedProduct(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): product = None - if context.active_object.BIMAssignedProductProperties.relating_product: - product = tool.Ifc.get_entity(context.active_object.BIMAssignedProductProperties.relating_product) - core.edit_assigned_product(tool.Ifc, tool.Drawing, obj=context.active_object, product=product) + assert (obj := context.active_object) + props = tool.Drawing.get_object_assigned_product_props(obj) + if props.relating_product: + product = tool.Ifc.get_entity(props.relating_product) + core.edit_assigned_product(tool.Ifc, tool.Drawing, obj=obj, product=product) tool.Blender.update_viewport() @@ -3151,6 +3163,7 @@ class EnableEditingAssignedProduct(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): + assert context.active_object core.enable_editing_assigned_product(tool.Drawing, obj=context.active_object) @@ -3160,6 +3173,7 @@ class DisableEditingAssignedProduct(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): + assert context.active_object core.disable_editing_assigned_product(tool.Drawing, obj=context.active_object) @@ -3203,6 +3217,7 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator): document_type: Literal["SHEET", "TITLEBLOCK", "EMBEDDED"] def invoke(self, context, event): + assert context.window_manager self.props = tool.Drawing.get_document_props() sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id) if sheet.is_a("IfcDocumentInformation"): diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py index fa37e7a825..dffdbfb0fb 100644 --- a/src/bonsai/bonsai/bim/module/drawing/prop.py +++ b/src/bonsai/bonsai/bim/module/drawing/prop.py @@ -752,6 +752,12 @@ class LiteralProps(PropertyGroup): } return text_data + if TYPE_CHECKING: + attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + value: str + box_alignment: str + ifc_definition_id: int + class BIMTextProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing", default=False) @@ -802,6 +808,10 @@ class BIMAssignedProductProperties(PropertyGroup): is_editing_product: BoolProperty(name="Is Editing Product", default=False) relating_product: PointerProperty(name="Relating Product", type=bpy.types.Object, poll=relating_product_poll) + if TYPE_CHECKING: + is_editing_product: bool + relating_product: Union[bpy.types.Object, None] + annotation_classes = [ (x, *tool.Drawing.ANNOTATION_TYPES_DATA[x][:3], i) for i, x in enumerate(tool.Drawing.ANNOTATION_TYPES_DATA) diff --git a/src/bonsai/bonsai/bim/module/drawing/ui.py b/src/bonsai/bonsai/bim/module/drawing/ui.py index 1b11340e8e..cb6162ee17 100644 --- a/src/bonsai/bonsai/bim/module/drawing/ui.py +++ b/src/bonsai/bonsai/bim/module/drawing/ui.py @@ -494,17 +494,19 @@ class BIM_PT_product_assignments(Panel): @classmethod def poll(cls, context): if not tool.Ifc.get() or not context.active_object: - return + return False element = tool.Ifc.get_entity(context.active_object) if not element: - return + return False return element.is_a("IfcAnnotation") def draw(self, context): if not ProductAssignmentsData.is_loaded: ProductAssignmentsData.load() - props = context.active_object.BIMAssignedProductProperties + assert self.layout + assert (obj := context.active_object) + props = tool.Drawing.get_object_assigned_product_props(obj) if props.is_editing_product: row = self.layout.row(align=True) diff --git a/src/bonsai/bonsai/bim/module/geometry/data.py b/src/bonsai/bonsai/bim/module/geometry/data.py index 2522615885..27670ee1d7 100644 --- a/src/bonsai/bonsai/bim/module/geometry/data.py +++ b/src/bonsai/bonsai/bim/module/geometry/data.py @@ -23,6 +23,7 @@ import ifcopenshell.util.placement import ifcopenshell.util.schema import ifcopenshell.util.unit import bonsai.tool as tool +from typing import Any, Union from mathutils import Vector @@ -45,13 +46,12 @@ class ViewportData: cls.data = {"mode": cls.mode()} @classmethod - def mode(cls): + def mode(cls) -> tool.Blender.BLENDER_ENUM_ITEMS: obj_mode = ("OBJECT", "IFC Object Mode", "View and move the placements of objects", "OBJECT_DATAMODE", 0) item_mode = ("ITEM", "IFC Item Mode", "View individual representation items", "MESH_DATA", 1) edit_mode = ("EDIT", "IFC Edit Mode", "Edit representation items", "EDITMODE_HLT", 2) obj = bpy.context.active_object - element = tool.Ifc.get_entity(obj) modes: list[tuple[str, str, str, str, int]] = [obj_mode] gprops = tool.Geometry.get_geometry_props() @@ -61,13 +61,14 @@ class ViewportData: if not obj: return modes + element = tool.Ifc.get_entity(obj) pprops = tool.Project.get_project_props() if obj in pprops.clipping_planes_objs: pass elif element: if tool.Geometry.is_locked(element): pass - elif obj.data and tool.Geometry.is_profile_based(obj.data): + elif obj.data and tool.Geometry.has_mesh_properties(obj.data) and tool.Geometry.is_profile_based(obj.data): modes.append(edit_mode) elif element.is_a("IfcRelSpaceBoundary"): modes.append(edit_mode) @@ -88,7 +89,7 @@ class ViewportData: class RepresentationsData: - data = {} + data: dict[str, Any] = {} is_loaded = False @classmethod @@ -102,10 +103,12 @@ class RepresentationsData: cls.is_loaded = True @classmethod - def representations(cls): - results = [] + def representations(cls) -> list[dict[str, Any]]: + results: list[dict[str, Any]] = [] obj = tool.Geometry.get_active_or_representation_obj() + assert obj element = tool.Ifc.get_entity(obj) + assert element active_representation_id = None active_representation = tool.Geometry.get_active_representation(obj) @@ -132,8 +135,8 @@ class RepresentationsData: return results @classmethod - def contexts(cls): - results = [] + def contexts(cls) -> tool.Blender.BLENDER_ENUM_ITEMS: + results: list[tuple[str, str, str]] = [] for element in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False): results.append((str(element.id()), element.ContextType or "Unnamed", "")) for element in tool.Ifc.get().by_type("IfcGeometricRepresentationSubContext", include_subtypes=False): @@ -219,9 +222,12 @@ class ConnectionsData: cls.is_loaded = True @classmethod - def connections(cls): - results = [] - element = tool.Ifc.get_entity(bpy.context.active_object) + def connections(cls) -> list[dict[str, Any]]: + results: list[dict[str, Any]] = [] + obj = bpy.context.active_object + assert obj + element = tool.Ifc.get_entity(obj) + assert element connected_to = getattr(element, "ConnectedTo", []) connected_from = getattr(element, "ConnectedFrom", []) @@ -285,13 +291,16 @@ class ConnectionsData: return results @classmethod - def is_connection_realization(cls): - element = tool.Ifc.get_entity(bpy.context.active_object) + def is_connection_realization(cls) -> Union[list[dict[str, Any]], None]: + obj = bpy.context.active_object + assert obj + element = tool.Ifc.get_entity(obj) + assert element connections = getattr(element, "IsConnectionRealization", None) if not connections: return - results = [] + results: list[dict[str, Any]] = [] for rel in connections: data = { "realizing_elements_connection_type": rel.ConnectionType, @@ -322,16 +331,19 @@ class DerivedCoordinatesData: cls.is_loaded = True @classmethod - def load_z_values(cls): + def load_z_values(cls) -> None: cls.z_values = [ (bpy.context.active_object.matrix_world @ Vector(co))[2] for co in bpy.context.active_object.bound_box ] @classmethod - def load_collection(cls): + def load_collection(cls) -> None: cls.collection = None cls.collection_z = 0 - element = tool.Ifc.get_entity(bpy.context.active_object) + obj = bpy.context.active_object + if not obj: + return + element = tool.Ifc.get_entity(obj) if not element: return parent = ifcopenshell.util.element.get_aggregate(element) @@ -392,7 +404,7 @@ class PlacementData: @classmethod def load(cls): - cls.data = {"has_placement": cls.has_placement()} + cls.data: dict[str, Any] = {"has_placement": cls.has_placement()} props = tool.Georeference.get_georeference_props() obj = bpy.context.active_object @@ -415,13 +427,14 @@ class PlacementData: return False @classmethod - def original_xyz(cls, obj): + def original_xyz(cls, obj: bpy.types.Object) -> list[float]: unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) props = tool.Georeference.get_georeference_props() + translation = obj.matrix_world.translation xyz = ifcopenshell.util.geolocation.xyz2enh( - obj.matrix_world[0][3], - obj.matrix_world[1][3], - obj.matrix_world[2][3], + translation[0], + translation[1], + translation[2], float(props.blender_offset_x) * unit_scale, float(props.blender_offset_y) * unit_scale, float(props.blender_offset_z) * unit_scale, diff --git a/src/bonsai/bonsai/bim/module/georeference/prop.py b/src/bonsai/bonsai/bim/module/georeference/prop.py index 58a3088b86..0a5eecf4c1 100644 --- a/src/bonsai/bonsai/bim/module/georeference/prop.py +++ b/src/bonsai/bonsai/bim/module/georeference/prop.py @@ -112,7 +112,9 @@ def update_blender_coordinates(self: "BIMGeoreferenceProperties", context: bpy.t props.is_updating_coordinates = True blender_coordinates = tool.Georeference.get_coordinates("blender") local_coordinates = ifcopenshell.util.geolocation.xyz2enh( - *blender_coordinates, + blender_coordinates[0], + blender_coordinates[1], + blender_coordinates[2], float(props.blender_offset_x), float(props.blender_offset_y), float(props.blender_offset_z), diff --git a/src/bonsai/bonsai/bim/module/owner/data.py b/src/bonsai/bonsai/bim/module/owner/data.py index 1637b41687..4fb53a6da1 100644 --- a/src/bonsai/bonsai/bim/module/owner/data.py +++ b/src/bonsai/bonsai/bim/module/owner/data.py @@ -17,7 +17,9 @@ # along with Bonsai. If not, see . import bpy +import ifcopenshell import bonsai.tool as tool +from typing import Any, Union from ifcopenshell.util.doc import get_entity_doc @@ -31,42 +33,46 @@ def refresh(): class RolesAddressesData: @classmethod - def get_roles(cls, parent): - results = [] + def get_roles(cls, parent: ifcopenshell.entity_instance) -> list[dict[str, Any]]: + props = tool.Owner.get_owner_props() + results: list[dict[str, Any]] = [] for role in parent.Roles or []: results.append( { "id": role.id(), - "is_editing": bpy.context.scene.BIMOwnerProperties.active_role_id == role.id(), + "is_editing": props.active_role_id == role.id(), "label": role.UserDefinedRole or role.Role, - "props": bpy.context.scene.BIMOwnerProperties.role_attributes, + "props": props.role_attributes, } ) return results @classmethod - def get_addresses(cls, parent): - results = [] + def get_addresses(cls, parent: ifcopenshell.entity_instance) -> list[dict[str, Any]]: + props = tool.Owner.get_owner_props() + results: list[dict[str, Any]] = [] for address in parent.Addresses or []: results.append( { "id": address.id(), - "is_editing": bpy.context.scene.BIMOwnerProperties.active_address_id == address.id(), + "is_editing": props.active_address_id == address.id(), "label": address.is_a(), - "props": bpy.context.scene.BIMOwnerProperties.address_attributes, + "props": props.address_attributes, "list_attributes": cls.get_address_list_attributes(address), } ) return results @classmethod - def get_address_list_attributes(cls, address): - results = [] - props = bpy.context.scene.BIMOwnerProperties + def get_address_list_attributes(cls, address: ifcopenshell.entity_instance) -> list[dict[str, Any]]: + results: list[dict[str, Any]] = [] + props = tool.Owner.get_owner_props() if address.is_a("IfcPostalAddress"): names = ["AddressLines"] elif address.is_a("IfcTelecomAddress"): names = ["TelephoneNumbers", "FacsimileNumbers", "ElectronicMailAddresses", "MessagingIDs"] + else: + assert False, f"Unexpected entity: {address}" for name in names: if name == "AddressLines": items = [{"id": id, "prop": prop} for id, prop in enumerate(props.address_lines)] @@ -78,6 +84,8 @@ class RolesAddressesData: items = [{"id": id, "prop": prop} for id, prop in enumerate(props.electronic_mail_addresses)] elif name == "MessagingIDs": items = [{"id": id, "prop": prop} for id, prop in enumerate(props.messaging_ids)] + else: + assert False, f"Unexpected name: {name}" results.append({"name": name, "items": items}) return results @@ -92,14 +100,15 @@ class PeopleData(RolesAddressesData): cls.is_loaded = True @classmethod - def get_people(cls): - people = [] + def get_people(cls) -> list[dict[str, Any]]: + props = tool.Owner.get_owner_props() + people: list[dict[str, Any]] = [] for person in tool.Ifc.get().by_type("IfcPerson"): roles = cls.get_roles(person) people.append( { "id": person.id(), - "props": bpy.context.scene.BIMOwnerProperties.person_attributes, + "props": props.person_attributes, "name": cls.get_person_name(person), "roles_label": ", ".join([r["label"] for r in roles]), "is_editing": cls.get_person_is_editing(person), @@ -112,7 +121,7 @@ class PeopleData(RolesAddressesData): return people @classmethod - def get_person_name(cls, person): + def get_person_name(cls, person: ifcopenshell.entity_instance) -> str: if tool.Ifc.get_schema() == "IFC2X3": name = person.Id else: @@ -124,20 +133,24 @@ class PeopleData(RolesAddressesData): return name @classmethod - def get_person_is_editing(cls, person): - return bpy.context.scene.BIMOwnerProperties.active_person_id == person.id() + def get_person_is_editing(cls, person: ifcopenshell.entity_instance) -> bool: + props = tool.Owner.get_owner_props() + return props.active_person_id == person.id() @classmethod - def get_person_list_attributes(cls, person): - results = [] - props = bpy.context.scene.BIMOwnerProperties - for name in ["MiddleNames", "PrefixTitles", "SuffixTitles"]: + def get_person_list_attributes(cls, person: ifcopenshell.entity_instance) -> list[dict[str, Any]]: + results: list[dict[str, Any]] = [] + props = tool.Owner.get_owner_props() + name: tool.Owner.PersonAttributeType + for name in ("MiddleNames", "PrefixTitles", "SuffixTitles"): if name == "MiddleNames": items = [{"id": id, "prop": prop} for id, prop in enumerate(props.middle_names)] elif name == "PrefixTitles": items = [{"id": id, "prop": prop} for id, prop in enumerate(props.prefix_titles)] elif name == "SuffixTitles": items = [{"id": id, "prop": prop} for id, prop in enumerate(props.suffix_titles)] + else: + assert False, name results.append({"name": name, "items": items}) return results @@ -152,17 +165,18 @@ class OrganisationsData(RolesAddressesData): cls.is_loaded = True @classmethod - def get_organisations(cls): - organisations = [] + def get_organisations(cls) -> list[dict[str, Any]]: + props = tool.Owner.get_owner_props() + organisations: list[dict[str, Any]] = [] for organisation in tool.Ifc.get().by_type("IfcOrganization"): roles = cls.get_roles(organisation) organisations.append( { "id": organisation.id(), - "props": bpy.context.scene.BIMOwnerProperties.organisation_attributes, + "props": props.organisation_attributes, "name": organisation.Name, "roles_label": ", ".join([r["label"] for r in roles]), - "is_editing": bpy.context.scene.BIMOwnerProperties.active_organisation_id == organisation.id(), + "is_editing": props.active_organisation_id == organisation.id(), "is_engaged": bool(organisation.Engages), "roles": roles, "addresses": cls.get_addresses(organisation), @@ -186,26 +200,27 @@ class OwnerData: cls.is_loaded = True @classmethod - def get_user_person(cls): + def get_user_person(cls) -> tool.Blender.BLENDER_ENUM_ITEMS: return [(str(p.id()), p[0] or "Unnamed", "") for p in tool.Ifc.get().by_type("IfcPerson")] @classmethod - def get_user_organisation(cls): + def get_user_organisation(cls) -> tool.Blender.BLENDER_ENUM_ITEMS: return [(str(p.id()), p[0] or "Unnamed", "") for p in tool.Ifc.get().by_type("IfcOrganization")] @classmethod - def can_add_user(cls): - return tool.Ifc.get().by_type("IfcPerson") and tool.Ifc.get().by_type("IfcOrganization") + def can_add_user(cls) -> bool: + return bool(tool.Ifc.get().by_type("IfcPerson") and tool.Ifc.get().by_type("IfcOrganization")) @classmethod - def get_users(cls): - results = [] + def get_users(cls) -> list[dict[str, Any]]: + props = tool.Owner.get_owner_props() + results: list[dict[str, Any]] = [] for user in tool.Ifc.get().by_type("IfcPersonAndOrganization"): results.append( { "id": user.id(), "label": "{} ({})".format(user.ThePerson[0] or "Unnamed", user.TheOrganization[0] or "Unnamed"), - "is_active": bpy.context.scene.BIMOwnerProperties.active_user_id == user.id(), + "is_active": props.active_user_id == user.id(), } ) return results @@ -224,48 +239,60 @@ class ActorData: cls.data["actors"] = cls.actors() @classmethod - def the_actor(cls): - if not (ifc_class := bpy.context.scene.BIMOwnerProperties.actor_type): + def the_actor(cls) -> tool.Blender.BLENDER_ENUM_ITEMS: + props = tool.Owner.get_owner_props() + if not (ifc_class := props.actor_type): ifc_class = cls.actor_type()[0][0] return [(str(p.id()), p[0] or "Unnamed", "") for p in tool.Ifc.get().by_type(ifc_class)] @classmethod - def actors(cls): - actors = [] - props = bpy.context.scene.BIMOwnerProperties + def actors(cls) -> list[dict[str, Any]]: + actors: list[dict[str, Any]] = [] + props = tool.Owner.get_owner_props() for actor in tool.Ifc.get().by_type(props.actor_class, include_subtypes=False): is_editing = props.active_actor_id == actor.id() - if actor.TheActor.is_a("IfcPerson"): - the_actor = actor.TheActor.Identification or "N/A" - elif actor.TheActor.is_a("IfcOrganization"): - the_actor = actor.TheActor.Identification or "N/A" - elif actor.TheActor.is_a("IfcPersonAndOrganization"): - the_actor = actor.TheActor.ThePerson.Identification or "N/A" - the_actor += "-" + actor.TheActor.TheOrganization.Identification or "N/A" + the_actor: ifcopenshell.entity_instance = actor.TheActor + if the_actor.is_a("IfcPerson"): + the_actor_ = the_actor.Identification or "N/A" + elif the_actor.is_a("IfcOrganization"): + the_actor_ = the_actor.Identification or "N/A" + elif the_actor.is_a("IfcPersonAndOrganization"): + the_actor_ = the_actor.ThePerson.Identification or "N/A" + the_actor_ += "-" + the_actor.TheOrganization.Identification or "N/A" + else: + assert False, the_actor actors.append( - {"id": actor.id(), "name": actor.Name or "Unnamed", "the_actor": the_actor, "is_editing": is_editing} + { + "id": actor.id(), + "name": actor.Name or "Unnamed", + "the_actor": the_actor_, + "is_editing": is_editing, + } ) return actors @classmethod - def actor_class(cls) -> list[tuple[str, str, str]]: + def actor_class(cls) -> tool.Blender.BLENDER_ENUM_ITEMS: version = tool.Ifc.get_schema() + actor_doc = get_entity_doc(version, "IfcActor") + occupant_doc = get_entity_doc(version, "IfcOccupant") + assert actor_doc and occupant_doc return [ - ("IfcActor", "Actor", get_entity_doc(version, "IfcActor").get("description", "")), - ("IfcOccupant", "Occupant", get_entity_doc(version, "IfcOccupant").get("description", "")), + ("IfcActor", "Actor", actor_doc.get("description", "")), + ("IfcOccupant", "Occupant", occupant_doc.get("description", "")), ] @classmethod - def actor_type(cls) -> list[tuple[str, str, str]]: + def actor_type(cls) -> tool.Blender.BLENDER_ENUM_ITEMS: version = tool.Ifc.get_schema() + person_doc = get_entity_doc(version, "IfcPerson") + organization_doc = get_entity_doc(version, "IfcOrganization") + pao_doc = get_entity_doc(version, "IfcPersonAndOrganization") + assert person_doc and organization_doc and pao_doc return [ - ("IfcPerson", "Person", get_entity_doc(version, "IfcPerson").get("description", "")), - ("IfcOrganization", "Organisation", get_entity_doc(version, "IfcOrganization").get("description", "")), - ( - "IfcPersonAndOrganization", - "User", - get_entity_doc(version, "IfcPersonAndOrganization").get("description", ""), - ), + ("IfcPerson", "Person", person_doc.get("description", "")), + ("IfcOrganization", "Organisation", organization_doc.get("description", "")), + ("IfcPersonAndOrganization", "User", pao_doc.get("description", "")), ] @@ -279,25 +306,31 @@ class ObjectActorData: cls.data = {"actor": cls.actor(), "actors": cls.actors()} @classmethod - def actor(cls): + def actor(cls) -> tool.Blender.BLENDER_ENUM_ITEMS: return [(str(p.id()), p.Name or "Unnamed", "") for p in tool.Ifc.get().by_type("IfcActor")] @classmethod - def actors(cls): - results = [] - element = tool.Ifc.get_entity(bpy.context.active_object) + def actors(cls) -> list[dict[str, Any]]: + results: list[dict[str, Any]] = [] + obj = bpy.context.active_object + if not obj: + return results + element = tool.Ifc.get_entity(obj) if not element: return results for rel in getattr(element, "HasAssignments", []): if rel.is_a("IfcRelAssignsToActor"): actor = rel.RelatingActor - if actor.TheActor.is_a("IfcPerson"): - roles = cls.get_roles(actor.TheActor) - elif actor.TheActor.is_a("IfcOrganization"): - roles = cls.get_roles(actor.TheActor) - elif actor.TheActor.is_a("IfcPersonAndOrganization"): - roles = cls.get_roles(actor.TheActor.ThePerson) - roles.extend(cls.get_roles(actor.TheActor.TheOrganization)) + the_actor: ifcopenshell.entity_instance = actor.TheActor + if the_actor.is_a("IfcPerson"): + roles = cls.get_roles(the_actor) + elif the_actor.is_a("IfcOrganization"): + roles = cls.get_roles(the_actor) + elif the_actor.is_a("IfcPersonAndOrganization"): + roles = cls.get_roles(the_actor.ThePerson) + roles.extend(cls.get_roles(the_actor.TheOrganization)) + else: + assert False, the_actor role = ", ".join(roles) results.append( {"id": actor.id(), "name": actor.Name or "Unnamed", "role": role, "ifc_class": actor.is_a()} @@ -305,5 +338,5 @@ class ObjectActorData: return results @classmethod - def get_roles(cls, parent): + def get_roles(cls, parent: ifcopenshell.entity_instance) -> list[Union[str, None]]: return [r.UserDefinedRole or r.Role for r in parent.Roles or []] diff --git a/src/bonsai/bonsai/bim/module/owner/operator.py b/src/bonsai/bonsai/bim/module/owner/operator.py index 4a4a014a9e..5a5763bfb7 100644 --- a/src/bonsai/bonsai/bim/module/owner/operator.py +++ b/src/bonsai/bonsai/bim/module/owner/operator.py @@ -19,25 +19,35 @@ import bpy import bonsai.tool as tool import bonsai.core.owner as core +from ifcopenshell.api.owner.add_address import ADDRESS_TYPE +from typing import TYPE_CHECKING, get_args + +if TYPE_CHECKING: + import bpy._typing.rna_enums as rna_enums -class EnableEditingPerson(bpy.types.Operator, tool.Ifc.Operator): +class EnableEditingPerson(bpy.types.Operator): bl_idname = "bim.enable_editing_person" bl_label = "Enable Editing Person" bl_options = {"REGISTER", "UNDO"} - person: bpy.props.IntProperty() + person: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] - def _execute(self, context): + if TYPE_CHECKING: + person: int + + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.enable_editing_person(tool.Owner, person=tool.Ifc.get().by_id(self.person)) + return {"FINISHED"} -class DisableEditingPerson(bpy.types.Operator, tool.Ifc.Operator): +class DisableEditingPerson(bpy.types.Operator): bl_idname = "bim.disable_editing_person" bl_label = "Disable Editing Person" bl_options = {"REGISTER", "UNDO"} - def _execute(self, context): + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.disable_editing_person(tool.Owner) + return {"FINISHED"} class AddPerson(bpy.types.Operator, tool.Ifc.Operator): @@ -45,7 +55,7 @@ class AddPerson(bpy.types.Operator, tool.Ifc.Operator): bl_label = "Add Person" bl_options = {"REGISTER", "UNDO"} - def _execute(self, context): + def _execute(self, context) -> None: core.add_person(tool.Ifc) @@ -62,57 +72,81 @@ class RemovePerson(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_person" bl_label = "Remove Person" bl_options = {"REGISTER", "UNDO"} - person: bpy.props.IntProperty() + person: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + person: int def _execute(self, context): core.remove_person(tool.Ifc, person=tool.Ifc.get().by_id(self.person)) -class AddPersonAttribute(bpy.types.Operator, tool.Ifc.Operator): +class AddPersonAttribute(bpy.types.Operator): bl_idname = "bim.add_person_attribute" bl_label = "Add Person Attribute" bl_options = {"REGISTER", "UNDO"} - name: bpy.props.StringProperty() + name: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration] + items=tuple((i, i, "") for i in get_args(tool.Owner.PersonAttributeType)), + ) - def _execute(self, context): + if TYPE_CHECKING: + name: tool.Owner.PersonAttributeType # pyright: ignore[reportIncompatibleVariableOverride] + + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.add_person_attribute(tool.Owner, name=self.name) + return {"FINISHED"} -class RemovePersonAttribute(bpy.types.Operator, tool.Ifc.Operator): +class RemovePersonAttribute(bpy.types.Operator): bl_idname = "bim.remove_person_attribute" bl_label = "Remove Person Attribute" bl_options = {"REGISTER", "UNDO"} - name: bpy.props.StringProperty() - id: bpy.props.IntProperty() + name: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration] + items=tuple((i, i, "") for i in get_args(tool.Owner.PersonAttributeType)), + ) + id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] - def _execute(self, context): + if TYPE_CHECKING: + name: tool.Owner.PersonAttributeType # pyright: ignore[reportIncompatibleVariableOverride] + id: int + + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.remove_person_attribute(tool.Owner, name=self.name, id=self.id) + return {"FINISHED"} -class EnableEditingRole(bpy.types.Operator, tool.Ifc.Operator): +class EnableEditingRole(bpy.types.Operator): bl_idname = "bim.enable_editing_role" bl_label = "Enable Editing Role" bl_options = {"REGISTER", "UNDO"} - role: bpy.props.IntProperty() + role: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] - def _execute(self, context): + if TYPE_CHECKING: + role: int + + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.enable_editing_role(tool.Owner, role=tool.Ifc.get().by_id(self.role)) + return {"FINISHED"} -class DisableEditingRole(bpy.types.Operator, tool.Ifc.Operator): +class DisableEditingRole(bpy.types.Operator): bl_idname = "bim.disable_editing_role" bl_label = "Disable Editing Role" bl_options = {"REGISTER", "UNDO"} - def _execute(self, context): + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.disable_editing_role(tool.Owner) + return {"FINISHED"} class AddRole(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_role" bl_label = "Add Role" bl_options = {"REGISTER", "UNDO"} - parent: bpy.props.IntProperty() + parent: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + parent: int def _execute(self, context): core.add_role(tool.Ifc, parent=tool.Ifc.get().by_id(self.parent)) @@ -131,7 +165,10 @@ class RemoveRole(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_role" bl_label = "Remove Role" bl_options = {"REGISTER", "UNDO"} - role: bpy.props.IntProperty() + role: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + role: int def _execute(self, context): core.remove_role(tool.Ifc, role=tool.Ifc.get().by_id(self.role)) @@ -141,51 +178,74 @@ class AddAddress(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_address" bl_label = "Add Address" bl_options = {"REGISTER", "UNDO"} - parent: bpy.props.IntProperty() - ifc_class: bpy.props.StringProperty() + parent: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + ifc_class: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration] + items=tuple((i, i, "") for i in get_args(ADDRESS_TYPE)), + ) + + if TYPE_CHECKING: + parent: int + ifc_class: ADDRESS_TYPE def _execute(self, context): core.add_address(tool.Ifc, parent=tool.Ifc.get().by_id(self.parent), ifc_class=self.ifc_class) -class AddAddressAttribute(bpy.types.Operator, tool.Ifc.Operator): +class AddAddressAttribute(bpy.types.Operator): bl_idname = "bim.add_address_attribute" bl_label = "Add Address Attribute" bl_options = {"REGISTER", "UNDO"} - name: bpy.props.StringProperty() + name: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration] + items=tuple((i, i, "") for i in get_args(tool.Owner.AddressAttributeType)), + ) - def _execute(self, context): + if TYPE_CHECKING: + name: tool.Owner.AddressAttributeType # pyright: ignore[reportIncompatibleVariableOverride] + + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.add_address_attribute(tool.Owner, name=self.name) + return {"FINISHED"} -class RemoveAddressAttribute(bpy.types.Operator, tool.Ifc.Operator): +class RemoveAddressAttribute(bpy.types.Operator): bl_idname = "bim.remove_address_attribute" bl_label = "Remove Address Attribute" bl_options = {"REGISTER", "UNDO"} - name: bpy.props.StringProperty() - id: bpy.props.IntProperty() + name: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration] + items=tuple((i, i, "") for i in get_args(tool.Owner.AddressAttributeType)), + ) - def _execute(self, context): + if TYPE_CHECKING: + name: tool.Owner.AddressAttributeType # pyright: ignore[reportIncompatibleVariableOverride] + id: int + + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.remove_address_attribute(tool.Owner, name=self.name, id=self.id) + return {"FINISHED"} -class EnableEditingAddress(bpy.types.Operator, tool.Ifc.Operator): +class EnableEditingAddress(bpy.types.Operator): bl_idname = "bim.enable_editing_address" bl_label = "Enable Editing Address" bl_options = {"REGISTER", "UNDO"} - address: bpy.props.IntProperty() + address: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] - def _execute(self, context): + if TYPE_CHECKING: + address: int + + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.enable_editing_address(tool.Owner, address=tool.Ifc.get().by_id(self.address)) + return {"FINISHED"} -class DisableEditingAddress(bpy.types.Operator, tool.Ifc.Operator): +class DisableEditingAddress(bpy.types.Operator): bl_idname = "bim.disable_editing_address" bl_label = "Disable Editing Address" bl_options = {"REGISTER", "UNDO"} - def _execute(self, context): + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.disable_editing_address(tool.Owner) + return {"FINISHED"} class EditAddress(bpy.types.Operator, tool.Ifc.Operator): @@ -201,29 +261,37 @@ class RemoveAddress(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_address" bl_label = "Remove Address" bl_options = {"REGISTER", "UNDO"} - address: bpy.props.IntProperty() + address: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + address: int def _execute(self, context): core.remove_address(tool.Ifc, address=tool.Ifc.get().by_id(self.address)) -class EnableEditingOrganisation(bpy.types.Operator, tool.Ifc.Operator): +class EnableEditingOrganisation(bpy.types.Operator): bl_idname = "bim.enable_editing_organisation" bl_label = "Enable Editing Organisation" bl_options = {"REGISTER", "UNDO"} - organisation: bpy.props.IntProperty() + organisation: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] - def _execute(self, context): + if TYPE_CHECKING: + organisation: int + + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.enable_editing_organisation(tool.Owner, organisation=tool.Ifc.get().by_id(self.organisation)) + return {"FINISHED"} -class DisableEditingOrganisation(bpy.types.Operator, tool.Ifc.Operator): +class DisableEditingOrganisation(bpy.types.Operator): bl_idname = "bim.disable_editing_organisation" bl_label = "Disable Editing Organisation" bl_options = {"REGISTER", "UNDO"} - def _execute(self, context): + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.disable_editing_organisation(tool.Owner) + return {"FINISHED"} class AddOrganisation(bpy.types.Operator, tool.Ifc.Operator): @@ -248,7 +316,10 @@ class RemoveOrganisation(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_organisation" bl_label = "Remove Organisation" bl_options = {"REGISTER", "UNDO"} - organisation: bpy.props.IntProperty() + organisation: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + organisation: int def _execute(self, context): core.remove_organisation(tool.Ifc, tool.Ifc.get().by_id(self.organisation)) @@ -258,8 +329,12 @@ class AddPersonAndOrganisation(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_person_and_organisation" bl_label = "Add Person And Organisation" bl_options = {"REGISTER", "UNDO"} - person: bpy.props.IntProperty() - organisation: bpy.props.IntProperty() + person: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + organisation: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + person: int + organisation: int def _execute(self, context): core.add_person_and_organisation( @@ -271,7 +346,10 @@ class RemovePersonAndOrganisation(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_person_and_organisation" bl_label = "Remove Person And Organisation" bl_options = {"REGISTER", "UNDO"} - person_and_organisation: bpy.props.IntProperty() + person_and_organisation: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + person_and_organisation: int def _execute(self, context): core.remove_person_and_organisation( @@ -279,24 +357,29 @@ class RemovePersonAndOrganisation(bpy.types.Operator, tool.Ifc.Operator): ) -class SetUser(bpy.types.Operator, tool.Ifc.Operator): +class SetUser(bpy.types.Operator): bl_idname = "bim.set_user" bl_label = "Set User" bl_options = {"REGISTER", "UNDO"} - user: bpy.props.IntProperty() + user: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] - def _execute(self, context): + if TYPE_CHECKING: + user: int + + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.set_user(tool.Owner, user=tool.Ifc.get().by_id(self.user)) + return {"FINISHED"} -class ClearUser(bpy.types.Operator, tool.Ifc.Operator): +class ClearUser(bpy.types.Operator): bl_idname = "bim.clear_user" bl_label = "Clear User" bl_options = {"REGISTER", "UNDO"} user: bpy.props.IntProperty() - def _execute(self, context): + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.clear_user(tool.Owner) + return {"FINISHED"} class AddActor(bpy.types.Operator, tool.Ifc.Operator): @@ -305,28 +388,33 @@ class AddActor(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - props = bpy.context.scene.BIMOwnerProperties + props = tool.Owner.get_owner_props() if props.the_actor: core.add_actor(tool.Ifc, ifc_class=props.actor_class, actor=tool.Ifc.get().by_id(int(props.the_actor))) -class EnableEditingActor(bpy.types.Operator, tool.Ifc.Operator): +class EnableEditingActor(bpy.types.Operator): bl_idname = "bim.enable_editing_actor" bl_label = "Enable Editing Actor" bl_options = {"REGISTER", "UNDO"} - actor: bpy.props.IntProperty() + actor: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] - def _execute(self, context): + if TYPE_CHECKING: + actor: int + + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.enable_editing_actor(tool.Owner, actor=tool.Ifc.get().by_id(self.actor)) + return {"FINISHED"} -class DisableEditingActor(bpy.types.Operator, tool.Ifc.Operator): +class DisableEditingActor(bpy.types.Operator): bl_idname = "bim.disable_editing_actor" bl_label = "Disable Editing Actor" bl_options = {"REGISTER", "UNDO"} - def _execute(self, context): + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": core.disable_editing_actor(tool.Owner) + return {"FINISHED"} class EditActor(bpy.types.Operator, tool.Ifc.Operator): @@ -342,7 +430,10 @@ class RemoveActor(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_actor" bl_label = "Remove Actor" bl_options = {"REGISTER", "UNDO"} - actor: bpy.props.IntProperty() + actor: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + actor: int def _execute(self, context): core.remove_actor(tool.Ifc, actor=tool.Ifc.get().by_id(self.actor)) @@ -352,21 +443,27 @@ class AssignActor(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_actor" bl_label = "Assign Actor" bl_options = {"REGISTER", "UNDO"} - actor: bpy.props.IntProperty() + actor: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + actor: int def _execute(self, context): - core.assign_actor( - tool.Ifc, actor=tool.Ifc.get().by_id(self.actor), element=tool.Ifc.get_entity(context.active_object) - ) + assert (obj := context.active_object) + assert (element := tool.Ifc.get_entity(obj)) + core.assign_actor(tool.Ifc, actor=tool.Ifc.get().by_id(self.actor), element=element) class UnassignActor(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.unassign_actor" bl_label = "Unassign Actor" bl_options = {"REGISTER", "UNDO"} - actor: bpy.props.IntProperty() + actor: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + actor: int def _execute(self, context): - core.unassign_actor( - tool.Ifc, actor=tool.Ifc.get().by_id(self.actor), element=tool.Ifc.get_entity(context.active_object) - ) + assert (obj := context.active_object) + assert (element := tool.Ifc.get_entity(obj)) + core.unassign_actor(tool.Ifc, actor=tool.Ifc.get().by_id(self.actor), element=element) diff --git a/src/bonsai/bonsai/bim/module/owner/prop.py b/src/bonsai/bonsai/bim/module/owner/prop.py index bd531aa342..801586dd20 100644 --- a/src/bonsai/bonsai/bim/module/owner/prop.py +++ b/src/bonsai/bonsai/bim/module/owner/prop.py @@ -20,6 +20,7 @@ import bpy import bonsai.tool as tool from bonsai.bim.prop import StrProperty, Attribute from bonsai.bim.module.owner.data import OwnerData, ActorData, ObjectActorData +from typing import TYPE_CHECKING, Literal from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -33,45 +34,45 @@ from bpy.props import ( ) -def get_user_person(self, context): +def get_user_person(self: "BIMOwnerProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS: if not OwnerData.is_loaded: OwnerData.load() return OwnerData.data["user_person"] -def get_user_organisation(self, context): +def get_user_organisation(self: "BIMOwnerProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS: if not OwnerData.is_loaded: OwnerData.load() return OwnerData.data["user_organisation"] -def get_the_actor(self, context): +def get_the_actor(self: "BIMOwnerProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS: if not ActorData.is_loaded: ActorData.load() return ActorData.data["the_actor"] -def get_actor(self, context): +def get_actor(self: "BIMOwnerProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS: if not ObjectActorData.is_loaded: ObjectActorData.load() return ObjectActorData.data["actor"] -def update_actor_type(self, context): +def update_actor_type(self: "BIMOwnerProperties", context: bpy.types.Context) -> None: ActorData.data["the_actor"] = ActorData.the_actor() -def update_actor_class(self, context): +def update_actor_class(self: "BIMOwnerProperties", context: bpy.types.Context) -> None: ActorData.data["actors"] = ActorData.actors() -def get_actor_class(self, context): +def get_actor_class(self: "BIMOwnerProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS: if not ActorData.is_loaded: ActorData.load() return ActorData.data["actor_class"] -def get_actor_type(self, context): +def get_actor_type(self: "BIMOwnerProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS: if not ActorData.is_loaded: ActorData.load() return ActorData.data["actor_type"] @@ -119,3 +120,30 @@ class BIMOwnerProperties(PropertyGroup): items=get_the_actor, name="Actor", description="This entity represents an individual human being." ) actor: EnumProperty(items=get_actor, name="Actor") + + if TYPE_CHECKING: + active_person_id: int + person_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + middle_names: bpy.types.bpy_prop_collection_idprop[StrProperty] + prefix_titles: bpy.types.bpy_prop_collection_idprop[StrProperty] + suffix_titles: bpy.types.bpy_prop_collection_idprop[StrProperty] + active_organisation_id: int + organisation_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + active_role_id: int + role_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + active_address_id: int + address_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + address_lines: bpy.types.bpy_prop_collection_idprop[StrProperty] + telephone_numbers: bpy.types.bpy_prop_collection_idprop[StrProperty] + facsimile_numbers: bpy.types.bpy_prop_collection_idprop[StrProperty] + electronic_mail_addresses: bpy.types.bpy_prop_collection_idprop[StrProperty] + messaging_ids: bpy.types.bpy_prop_collection_idprop[StrProperty] + user_person: str + user_organisation: str + active_user_id: int + active_actor_id: int + actor_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + actor_class: Literal["IfcActor", "IfcOccupant"] + actor_type: str + the_actor: str + actor: str diff --git a/src/bonsai/bonsai/bim/module/owner/ui.py b/src/bonsai/bonsai/bim/module/owner/ui.py index 6557dd04ca..233e4cf689 100644 --- a/src/bonsai/bonsai/bim/module/owner/ui.py +++ b/src/bonsai/bonsai/bim/module/owner/ui.py @@ -19,10 +19,11 @@ import bpy import bonsai.bim.helper import bonsai.tool as tool +from typing import Any from bonsai.bim.module.owner.data import PeopleData, OrganisationsData, OwnerData, ActorData, ObjectActorData -def draw_roles(box, parent): +def draw_roles(box: bpy.types.UILayout, parent: dict[str, Any]) -> None: row = box.row(align=True) row.label(text="Roles") op = row.operator("bim.add_role", icon="ADD", text="") @@ -41,7 +42,7 @@ def draw_roles(box, parent): row.operator("bim.remove_role", icon="X", text="").role = role["id"] -def draw_addresses(box, parent): +def draw_addresses(box: bpy.types.UILayout, parent: dict[str, Any]) -> None: row = box.row(align=True) row.label(text="Addresses") op = row.operator("bim.add_address", icon="LINK_BLEND", text="") @@ -87,12 +88,13 @@ class BIM_PT_people(bpy.types.Panel): @classmethod def poll(cls, context): - return tool.Ifc.get() + return bool(tool.Ifc.get()) def draw(self, context): if not PeopleData.is_loaded: PeopleData.load() + assert self.layout self.layout.use_property_split = True self.layout.use_property_decorate = False @@ -102,7 +104,8 @@ class BIM_PT_people(bpy.types.Panel): for person in PeopleData.data["people"]: self.draw_person(person) - def draw_person(self, person): + def draw_person(self, person: dict[str, Any]) -> None: + assert self.layout if person["is_editing"]: box = self.layout.box() row = box.row(align=True) @@ -146,12 +149,13 @@ class BIM_PT_organisations(bpy.types.Panel): @classmethod def poll(cls, context): - return tool.Ifc.get() + return bool(tool.Ifc.get()) def draw(self, context): if not OrganisationsData.is_loaded: OrganisationsData.load() + assert self.layout self.layout.use_property_split = True self.layout.use_property_decorate = False @@ -161,7 +165,8 @@ class BIM_PT_organisations(bpy.types.Panel): for organisation in OrganisationsData.data["organisations"]: self.draw_organisation(organisation) - def draw_organisation(self, organisation): + def draw_organisation(self, organisation: dict[str, Any]) -> None: + assert self.layout if organisation["is_editing"]: box = self.layout.box() row = box.row(align=True) @@ -193,15 +198,16 @@ class BIM_PT_owner(bpy.types.Panel): @classmethod def poll(cls, context): - return tool.Ifc.get() + return bool(tool.Ifc.get()) def draw(self, context): if not OwnerData.is_loaded: OwnerData.load() + assert self.layout self.layout.use_property_split = True self.layout.use_property_decorate = False - props = context.scene.BIMOwnerProperties + props = tool.Owner.get_owner_props() if not OwnerData.data["user_person"]: self.layout.label(text="No people found.") @@ -244,13 +250,14 @@ class BIM_PT_actor(bpy.types.Panel): @classmethod def poll(cls, context): - return tool.Ifc.get() + return bool(tool.Ifc.get()) def draw(self, context): if not ActorData.is_loaded: ActorData.load() - self.props = context.scene.BIMOwnerProperties + assert self.layout + self.props = tool.Owner.get_owner_props() self.layout.use_property_split = True self.layout.use_property_decorate = False @@ -268,7 +275,8 @@ class BIM_PT_actor(bpy.types.Panel): for actor in ActorData.data["actors"]: self.draw_actor(actor) - def draw_actor(self, actor): + def draw_actor(self, actor: dict[str, Any]) -> None: + assert self.layout if actor["is_editing"]: box = self.layout.box() row = box.row(align=True) @@ -295,13 +303,14 @@ class BIM_PT_object_actor(bpy.types.Panel): @classmethod def poll(cls, context): - return tool.Ifc.get() + return bool(tool.Ifc.get()) def draw(self, context): if not ObjectActorData.is_loaded: ObjectActorData.load() - self.props = context.scene.BIMOwnerProperties + assert self.layout + self.props = tool.Owner.get_owner_props() if not ObjectActorData.data["actor"]: row = self.layout.row(align=True) diff --git a/src/bonsai/bonsai/bim/module/resource/prop.py b/src/bonsai/bonsai/bim/module/resource/prop.py index b5f1360c4e..bb7d0a2bf7 100644 --- a/src/bonsai/bonsai/bim/module/resource/prop.py +++ b/src/bonsai/bonsai/bim/module/resource/prop.py @@ -58,7 +58,7 @@ def updateResourceName(self, context): attributes={"Name": self.name}, ) if props.active_resource_id == self.ifc_definition_id: - attribute = props.resource_attributes.get("Name") + attribute = props.resource_attributes["Name"] attribute.string_value = self.name bonsai.bim.module.resource.data.refresh() tool.Sequence.refresh_task_resources() diff --git a/src/bonsai/bonsai/core/owner.py b/src/bonsai/bonsai/core/owner.py index 13222a60b9..da9f2847ad 100644 --- a/src/bonsai/bonsai/core/owner.py +++ b/src/bonsai/bonsai/core/owner.py @@ -27,167 +27,171 @@ if TYPE_CHECKING: from ifcopenshell.api.owner.add_actor import ACTOR_TYPE -def add_person(ifc: tool.Ifc) -> ifcopenshell.entity_instance: +def add_person(ifc: type[tool.Ifc]) -> ifcopenshell.entity_instance: return ifc.run("owner.add_person") -def remove_person(ifc: tool.Ifc, person: ifcopenshell.entity_instance) -> None: +def remove_person(ifc: type[tool.Ifc], person: ifcopenshell.entity_instance) -> None: ifc.run("owner.remove_person", person=person) -def enable_editing_person(owner: tool.Owner, person: ifcopenshell.entity_instance) -> None: +def enable_editing_person(owner: type[tool.Owner], person: ifcopenshell.entity_instance) -> None: owner.set_person(person) owner.import_person_attributes() -def disable_editing_person(owner: tool.Owner) -> None: +def disable_editing_person(owner: type[tool.Owner]) -> None: owner.clear_person() -def edit_person(ifc: tool.Ifc, owner: tool.Owner) -> None: +def edit_person(ifc: type[tool.Ifc], owner: type[tool.Owner]) -> None: ifc.run("owner.edit_person", person=owner.get_person(), attributes=owner.export_person_attributes()) disable_editing_person(owner) -def add_person_attribute(owner: tool.Owner, name: str) -> None: +def add_person_attribute(owner: type[tool.Owner], name: tool.Owner.PersonAttributeType) -> None: owner.add_person_attribute(name) -def remove_person_attribute(owner: tool.Owner, name: str, id: int) -> None: +def remove_person_attribute(owner: type[tool.Owner], name: tool.Owner.PersonAttributeType, id: int) -> None: owner.remove_person_attribute(name, id) -def add_role(ifc: tool.Ifc, parent: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: +def add_role(ifc: type[tool.Ifc], parent: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: return ifc.run("owner.add_role", assigned_object=parent) -def remove_role(ifc: tool.Ifc, role: ifcopenshell.entity_instance) -> None: +def remove_role(ifc: type[tool.Ifc], role: ifcopenshell.entity_instance) -> None: ifc.run("owner.remove_role", role=role) -def enable_editing_role(owner: tool.Owner, role: ifcopenshell.entity_instance) -> None: +def enable_editing_role(owner: type[tool.Owner], role: ifcopenshell.entity_instance) -> None: owner.set_role(role) owner.import_role_attributes() -def disable_editing_role(owner: tool.Owner) -> None: +def disable_editing_role(owner: type[tool.Owner]) -> None: owner.clear_role() -def edit_role(ifc: tool.Ifc, owner: tool.Owner) -> None: +def edit_role(ifc: type[tool.Ifc], owner: type[tool.Owner]) -> None: ifc.run("owner.edit_role", role=owner.get_role(), attributes=owner.export_role_attributes()) owner.clear_role() def add_address( - ifc: tool.Ifc, parent: ifcopenshell.entity_instance, ifc_class: ADDRESS_TYPE = "IfcPostalAddress" + ifc: type[tool.Ifc], parent: ifcopenshell.entity_instance, ifc_class: ADDRESS_TYPE = "IfcPostalAddress" ) -> ifcopenshell.entity_instance: return ifc.run("owner.add_address", assigned_object=parent, ifc_class=ifc_class) -def remove_address(ifc: tool.Ifc, address: ifcopenshell.entity_instance) -> None: +def remove_address(ifc: type[tool.Ifc], address: ifcopenshell.entity_instance) -> None: ifc.run("owner.remove_address", address=address) -def enable_editing_address(owner: tool.Owner, address: ifcopenshell.entity_instance) -> None: +def enable_editing_address(owner: type[tool.Owner], address: ifcopenshell.entity_instance) -> None: owner.set_address(address) owner.import_address_attributes() -def disable_editing_address(owner: tool.Owner) -> None: +def disable_editing_address(owner: type[tool.Owner]) -> None: owner.clear_address() -def edit_address(ifc: tool.Ifc, owner: tool.Owner) -> None: +def edit_address(ifc: type[tool.Ifc], owner: type[tool.Owner]) -> None: address = owner.get_address() ifc.run("owner.edit_address", address=address, attributes=owner.export_address_attributes()) owner.clear_address() -def add_address_attribute(owner: tool.Owner, name: str) -> None: +def add_address_attribute(owner: type[tool.Owner], name: tool.Owner.AddressAttributeType) -> None: owner.add_address_attribute(name) -def remove_address_attribute(owner: tool.Owner, name: str, id: int) -> None: +def remove_address_attribute(owner: type[tool.Owner], name: tool.Owner.AddressAttributeType, id: int) -> None: owner.remove_address_attribute(name, id) -def add_organisation(ifc: tool.Ifc) -> ifcopenshell.entity_instance: +def add_organisation(ifc: type[tool.Ifc]) -> ifcopenshell.entity_instance: return ifc.run("owner.add_organisation") -def remove_organisation(ifc: tool.Ifc, organisation: ifcopenshell.entity_instance) -> None: +def remove_organisation(ifc: type[tool.Ifc], organisation: ifcopenshell.entity_instance) -> None: ifc.run("owner.remove_organisation", organisation=organisation) -def enable_editing_organisation(owner: tool.Owner, organisation: ifcopenshell.entity_instance) -> None: +def enable_editing_organisation(owner: type[tool.Owner], organisation: ifcopenshell.entity_instance) -> None: owner.set_organisation(organisation) owner.import_organisation_attributes() -def disable_editing_organisation(owner: tool.Owner) -> None: +def disable_editing_organisation(owner: type[tool.Owner]) -> None: owner.clear_organisation() -def edit_organisation(ifc: tool.Ifc, owner: tool.Owner) -> None: +def edit_organisation(ifc: type[tool.Ifc], owner: type[tool.Owner]) -> None: organisation = owner.get_organisation() ifc.run("owner.edit_organisation", organisation=organisation, attributes=owner.export_organisation_attributes()) owner.clear_organisation() def add_person_and_organisation( - ifc: tool.Ifc, person: ifcopenshell.entity_instance, organisation: ifcopenshell.entity_instance + ifc: type[tool.Ifc], person: ifcopenshell.entity_instance, organisation: ifcopenshell.entity_instance ) -> ifcopenshell.entity_instance: return ifc.run("owner.add_person_and_organisation", person=person, organisation=organisation) def remove_person_and_organisation( - ifc: tool.Ifc, owner: tool.Owner, person_and_organisation: ifcopenshell.entity_instance + ifc: type[tool.Ifc], owner: type[tool.Owner], person_and_organisation: ifcopenshell.entity_instance ) -> None: if owner.get_user() == person_and_organisation: owner.clear_user() ifc.run("owner.remove_person_and_organisation", person_and_organisation=person_and_organisation) -def set_user(owner: tool.Owner, user: ifcopenshell.entity_instance) -> None: +def set_user(owner: type[tool.Owner], user: ifcopenshell.entity_instance) -> None: owner.set_user(user) -def get_user(owner: tool.Owner) -> Union[ifcopenshell.entity_instance, None]: +def get_user(owner: type[tool.Owner]) -> Union[ifcopenshell.entity_instance, None]: return owner.get_user() -def clear_user(owner: tool.Owner) -> None: +def clear_user(owner: type[tool.Owner]) -> None: owner.clear_user() def add_actor( - ifc: tool.Ifc, ifc_class: ACTOR_TYPE, actor: ifcopenshell.entity_instance + ifc: type[tool.Ifc], ifc_class: ACTOR_TYPE, actor: ifcopenshell.entity_instance ) -> ifcopenshell.entity_instance: return ifc.run("owner.add_actor", ifc_class=ifc_class, actor=actor) -def remove_actor(ifc: tool.Ifc, actor: ifcopenshell.entity_instance) -> None: +def remove_actor(ifc: type[tool.Ifc], actor: ifcopenshell.entity_instance) -> None: ifc.run("owner.remove_actor", actor=actor) -def enable_editing_actor(owner: tool.Owner, actor: ifcopenshell.entity_instance) -> None: +def enable_editing_actor(owner: type[tool.Owner], actor: ifcopenshell.entity_instance) -> None: owner.set_actor(actor) owner.import_actor_attributes(actor) -def disable_editing_actor(owner: tool.Owner) -> None: +def disable_editing_actor(owner: type[tool.Owner]) -> None: owner.clear_actor() -def edit_actor(ifc: tool.Ifc, owner: tool.Owner) -> None: +def edit_actor(ifc: type[tool.Ifc], owner: type[tool.Owner]) -> None: ifc.run("owner.edit_actor", actor=owner.get_actor(), attributes=owner.export_actor_attributes()) disable_editing_actor(owner) -def assign_actor(ifc: tool.Ifc, actor: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance) -> None: +def assign_actor( + ifc: type[tool.Ifc], actor: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance +) -> None: ifc.run("owner.assign_actor", relating_actor=actor, related_object=element) -def unassign_actor(ifc: tool.Ifc, actor: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance) -> None: +def unassign_actor( + ifc: type[tool.Ifc], actor: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance +) -> None: ifc.run("owner.unassign_actor", relating_actor=actor, related_object=element) diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 3f16024fb8..77dfa96770 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -71,7 +71,7 @@ class Blender(bonsai.core.tool.Blender): OBJECT_TYPES_THAT_SUPPORT_EDIT_GPENCIL_MODE = ("GPENCIL",) TYPE_MANAGER_ICON = "LIGHTPROBE_VOLUME" - BLENDER_ENUM_ITEM = Union[tuple[str, str, str], tuple[str, str, str, str], tuple[str, str, str, str, str]] + BLENDER_ENUM_ITEM = Union[tuple[str, str, str], tuple[str, str, str, int], tuple[str, str, str, str, int]] """ Options: @@ -81,6 +81,7 @@ class Blender(bonsai.core.tool.Blender): - (identifier, name, description, icon, number) """ + BLENDER_ENUM_ITEMS = list[BLENDER_ENUM_ITEM] @classmethod def activate_camera(cls, obj: bpy.types.Object) -> None: diff --git a/src/bonsai/bonsai/tool/context.py b/src/bonsai/bonsai/tool/context.py index 62c0a4111a..c3ca9cb797 100644 --- a/src/bonsai/bonsai/tool/context.py +++ b/src/bonsai/bonsai/tool/context.py @@ -16,22 +16,33 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations import bpy import bonsai.bim.helper import bonsai.tool as tool import bonsai.core.tool import ifcopenshell -from typing import Any, Union +from typing import Any, Union, TYPE_CHECKING + +if TYPE_CHECKING: + from bonsai.bim.prop import Attribute + from bonsai.bim.module.context.prop import BIMContextProperties class Context(bonsai.core.tool.Context): + @classmethod + def get_context_props(cls) -> BIMContextProperties: + assert bpy.context.scene + return bpy.context.scene.BIMContextProperties # pyright: ignore[reportAttributeAccessIssue] + @classmethod def set_context(cls, context: ifcopenshell.entity_instance) -> None: - bpy.context.scene.BIMContextProperties.active_context_id = context.id() + props = cls.get_context_props() + props.active_context_id = context.id() @classmethod def import_attributes(cls) -> None: - props = bpy.context.scene.BIMContextProperties + props = cls.get_context_props() props.context_attributes.clear() context = cls.get_context() @@ -52,20 +63,22 @@ class Context(bonsai.core.tool.Context): @classmethod def clear_context(cls) -> None: - bpy.context.scene.BIMContextProperties.active_context_id = 0 + props = cls.get_context_props() + props.active_context_id = 0 @classmethod def get_context(cls) -> ifcopenshell.entity_instance: - return tool.Ifc.get().by_id(bpy.context.scene.BIMContextProperties.active_context_id) + props = cls.get_context_props() + return tool.Ifc.get().by_id(props.active_context_id) @classmethod def export_attributes(cls) -> dict[str, Any]: - def callback(attributes, blender_attribute) -> bool: + props = cls.get_context_props() + + def callback(attributes: dict[str, Any], blender_attribute: Attribute) -> bool: if blender_attribute.name == "Precision": attributes["Precision"] = float(blender_attribute.get_value()) return True return False - return bonsai.bim.helper.export_attributes( - bpy.context.scene.BIMContextProperties.context_attributes, callback=callback - ) + return bonsai.bim.helper.export_attributes(props.context_attributes, callback=callback) diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py index b728173dfe..1e5a70f77c 100644 --- a/src/bonsai/bonsai/tool/drawing.py +++ b/src/bonsai/bonsai/tool/drawing.py @@ -61,6 +61,7 @@ if TYPE_CHECKING: BIMAnnotationProperties, BIMTextProperties, BIMCameraProperties, + BIMAssignedProductProperties, ) from bonsai.bim.module.drawing.prop import Drawing as DrawingProperties @@ -105,15 +106,17 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def get_document_props(cls) -> DocProperties: - return bpy.context.scene.DocProperties + assert (scene := bpy.context.scene) + return scene.DocProperties # pyright: ignore[reportAttributeAccessIssue] @classmethod def get_annotation_props(cls) -> BIMAnnotationProperties: - return bpy.context.scene.BIMAnnotationProperties + assert (scene := bpy.context.scene) + return scene.BIMAnnotationProperties # pyright: ignore[reportAttributeAccessIssue] @classmethod def get_text_props(cls, obj: bpy.types.Object) -> BIMTextProperties: - return obj.BIMTextProperties + return obj.BIMTextProperties # pyright: ignore[reportAttributeAccessIssue] @classmethod def get_camera_props(cls, camera: Union[bpy.types.Object, bpy.types.Camera]) -> BIMCameraProperties: @@ -124,7 +127,11 @@ class Drawing(bonsai.core.tool.Drawing): data = camera else: assert isinstance(data := camera.data, bpy.types.Camera) - return data.BIMCameraProperties + return data.BIMCameraProperties # pyright: ignore[reportAttributeAccessIssue] + + @classmethod + def get_object_assigned_product_props(cls, obj: bpy.types.Object) -> BIMAssignedProductProperties: + return obj.BIMAssignedProductProperties # pyright: ignore[reportAttributeAccessIssue] @classmethod def canonicalise_class_name(cls, name: str) -> str: @@ -406,7 +413,8 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def disable_editing_assigned_product(cls, obj: bpy.types.Object) -> None: - obj.BIMAssignedProductProperties.is_editing_product = False + props = cls.get_object_assigned_product_props(obj) + props.is_editing_product = False @classmethod def enable_editing(cls, obj: bpy.types.Object) -> None: @@ -447,7 +455,8 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def enable_editing_assigned_product(cls, obj: bpy.types.Object) -> None: - obj.BIMAssignedProductProperties.is_editing_product = True + props = cls.get_object_assigned_product_props(obj) + props.is_editing_product = True @classmethod def ensure_unique_drawing_name(cls, name: str) -> str: @@ -1045,11 +1054,14 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def import_assigned_product(cls, obj: bpy.types.Object) -> None: element = tool.Ifc.get_entity(obj) + assert element product = cls.get_assigned_product(element) + props = cls.get_object_assigned_product_props(obj) if product: - obj.BIMAssignedProductProperties.relating_product = tool.Ifc.get_object(product) + assert isinstance(product_obj := tool.Ifc.get_object(product), bpy.types.Object) + props.relating_product = product_obj else: - obj.BIMAssignedProductProperties.relating_product = None + props.relating_product = None @classmethod def open_with_user_command(cls, user_command: str, path: str) -> None: diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index af456c4170..92fed10700 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -1030,7 +1030,7 @@ class Geometry(bonsai.core.tool.Geometry): return item.is_a("IfcSweptAreaSolid") or item.is_a("IfcHalfSpaceSolid") @classmethod - def is_profile_based(cls, data: bpy.types.Mesh) -> bool: + def is_profile_based(cls, data: TYPES_WITH_MESH_PROPERTIES) -> bool: props = tool.Geometry.get_mesh_props(data) return props.subshape_type == "PROFILE" diff --git a/src/bonsai/bonsai/tool/owner.py b/src/bonsai/bonsai/tool/owner.py index 8686a9b7de..46a23004d3 100644 --- a/src/bonsai/bonsai/tool/owner.py +++ b/src/bonsai/bonsai/tool/owner.py @@ -16,23 +16,35 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations import bpy import bonsai.core.tool import bonsai.tool as tool import bonsai.bim.helper import ifcopenshell -from typing import Union, Any +from typing import Union, Any, TYPE_CHECKING, Literal +from typing_extensions import assert_never + +if TYPE_CHECKING: + from bonsai.bim.module.owner.prop import BIMOwnerProperties class Owner(bonsai.core.tool.Owner): + @classmethod + def get_owner_props(cls) -> BIMOwnerProperties: + return bpy.context.scene.BIMOwnerProperties + @classmethod def set_user(cls, user: ifcopenshell.entity_instance) -> None: - bpy.context.scene.BIMOwnerProperties.active_user_id = user.id() + props = cls.get_owner_props() + props.active_user_id = user.id() @classmethod def get_user(cls) -> Union[ifcopenshell.entity_instance, None]: - if bpy.context.scene.BIMOwnerProperties.active_user_id: - return tool.Ifc.get().by_id(bpy.context.scene.BIMOwnerProperties.active_user_id) + props = cls.get_owner_props() + active_user_id = props.active_user_id + if active_user_id: + return tool.Ifc.get().by_id(active_user_id) elif tool.Ifc.get_schema() == "IFC2X3": users = tool.Ifc.get().by_type("IfcPersonAndOrganization") if users: @@ -40,15 +52,17 @@ class Owner(bonsai.core.tool.Owner): @classmethod def clear_user(cls) -> None: - bpy.context.scene.BIMOwnerProperties.active_user_id = 0 + props = cls.get_owner_props() + props.active_user_id = 0 @classmethod def set_address(cls, address: ifcopenshell.entity_instance) -> None: - bpy.context.scene.BIMOwnerProperties.active_address_id = address.id() + props = cls.get_owner_props() + props.active_address_id = address.id() @classmethod def import_address_attributes(cls) -> None: - props = bpy.context.scene.BIMOwnerProperties + props = props = cls.get_owner_props() props.address_attributes.clear() props.address_lines.clear() props.telephone_numbers.clear() @@ -58,7 +72,7 @@ class Owner(bonsai.core.tool.Owner): address = cls.get_address() - def callback(name, prop, data): + def callback(name: str, prop, data: dict[str, Any]) -> None: if name == "AddressLines": for line in data[name] or []: props.address_lines.add().name = line @@ -79,15 +93,17 @@ class Owner(bonsai.core.tool.Owner): @classmethod def clear_address(cls) -> None: - bpy.context.scene.BIMOwnerProperties.active_address_id = 0 + props = cls.get_owner_props() + props.active_address_id = 0 @classmethod def get_address(cls) -> ifcopenshell.entity_instance: - return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_address_id) + props = cls.get_owner_props() + return tool.Ifc().get().by_id(props.active_address_id) @classmethod def export_address_attributes(cls) -> dict[str, Any]: - props = bpy.context.scene.BIMOwnerProperties + props = cls.get_owner_props() attributes = bonsai.bim.helper.export_attributes(props.address_attributes) if cls.get_address().is_a("IfcPostalAddress"): attributes["AddressLines"] = [l.name for l in props.address_lines] or None @@ -98,9 +114,13 @@ class Owner(bonsai.core.tool.Owner): attributes["MessagingIDs"] = [l.name for l in props.messaging_ids] or None return attributes + AddressAttributeType = Literal[ + "AddressLines", "TelephoneNumbers", "FacsimileNumbers", "ElectronicMailAddresses", "MessagingIDs" + ] + @classmethod - def add_address_attribute(cls, name: str) -> None: - props = bpy.context.scene.BIMOwnerProperties + def add_address_attribute(cls, name: AddressAttributeType) -> None: + props = cls.get_owner_props() if name == "AddressLines": props.address_lines.add() elif name == "TelephoneNumbers": @@ -111,10 +131,12 @@ class Owner(bonsai.core.tool.Owner): props.electronic_mail_addresses.add() elif name == "MessagingIDs": props.messaging_ids.add() + else: + assert_never(name) @classmethod - def remove_address_attribute(cls, name: str, id: int) -> None: - props = bpy.context.scene.BIMOwnerProperties + def remove_address_attribute(cls, name: AddressAttributeType, id: int) -> None: + props = cls.get_owner_props() if name == "AddressLines": props.address_lines.remove(id) elif name == "TelephoneNumbers": @@ -125,47 +147,53 @@ class Owner(bonsai.core.tool.Owner): props.electronic_mail_addresses.remove(id) elif name == "MessagingIDs": props.messaging_ids.remove(id) + else: + assert_never(name) @classmethod def set_organisation(cls, organisation: ifcopenshell.entity_instance) -> None: - bpy.context.scene.BIMOwnerProperties.active_organisation_id = organisation.id() + props = cls.get_owner_props() + props.active_organisation_id = organisation.id() @classmethod def import_organisation_attributes(cls) -> None: - organisation = tool.Ifc.get().by_id(bpy.context.scene.BIMOwnerProperties.active_organisation_id) - props = bpy.context.scene.BIMOwnerProperties + props = cls.get_owner_props() + organisation = tool.Ifc.get().by_id(props.active_organisation_id) props.organisation_attributes.clear() bonsai.bim.helper.import_attributes("IfcOrganization", props.organisation_attributes, organisation.get_info()) @classmethod def clear_organisation(cls) -> None: - bpy.context.scene.BIMOwnerProperties.active_organisation_id = 0 + props = cls.get_owner_props() + props.active_organisation_id = 0 @classmethod def export_organisation_attributes(cls) -> dict[str, Any]: - props = bpy.context.scene.BIMOwnerProperties + props = cls.get_owner_props() attributes = bonsai.bim.helper.export_attributes(props.organisation_attributes) return attributes @classmethod def get_organisation(cls) -> ifcopenshell.entity_instance: - return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_organisation_id) + props = cls.get_owner_props() + return tool.Ifc().get().by_id(props.active_organisation_id) @classmethod def set_person(cls, person: ifcopenshell.entity_instance) -> None: - bpy.context.scene.BIMOwnerProperties.active_person_id = person.id() + props = cls.get_owner_props() + props.active_person_id = person.id() @classmethod def import_person_attributes(cls) -> None: - person = tool.Ifc.get().by_id(bpy.context.scene.BIMOwnerProperties.active_person_id) - props = bpy.context.scene.BIMOwnerProperties + props = cls.get_owner_props() + person = tool.Ifc.get().by_id(props.active_person_id) props.person_attributes.clear() props.middle_names.clear() props.prefix_titles.clear() props.suffix_titles.clear() - def callback(name, prop, data): + def callback(name: str, prop, data: dict[str, Any]) -> None: if name == "MiddleNames": for name in data["MiddleNames"] or []: props.middle_names.add().name = name or "" @@ -180,11 +208,12 @@ class Owner(bonsai.core.tool.Owner): @classmethod def clear_person(cls) -> None: - bpy.context.scene.BIMOwnerProperties.active_person_id = 0 + props = cls.get_owner_props() + props.active_person_id = 0 @classmethod def export_person_attributes(cls) -> dict[str, Any]: - props = bpy.context.scene.BIMOwnerProperties + props = cls.get_owner_props() attributes = bonsai.bim.helper.export_attributes(props.person_attributes) attributes["MiddleNames"] = [v.name for v in props.middle_names] if props.middle_names else None attributes["PrefixTitles"] = [v.name for v in props.prefix_titles] if props.prefix_titles else None @@ -193,69 +222,85 @@ class Owner(bonsai.core.tool.Owner): @classmethod def get_person(cls) -> ifcopenshell.entity_instance: - return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_person_id) + props = cls.get_owner_props() + return tool.Ifc().get().by_id(props.active_person_id) + + PersonAttributeType = Literal["MiddleNames", "PrefixTitles", "SuffixTitles"] @classmethod - def add_person_attribute(cls, name: str) -> None: + def add_person_attribute(cls, name: PersonAttributeType) -> None: + props = cls.get_owner_props() if name == "MiddleNames": - bpy.context.scene.BIMOwnerProperties.middle_names.add() + props.middle_names.add() elif name == "PrefixTitles": - bpy.context.scene.BIMOwnerProperties.prefix_titles.add() + props.prefix_titles.add() elif name == "SuffixTitles": - bpy.context.scene.BIMOwnerProperties.suffix_titles.add() + props.suffix_titles.add() + else: + assert_never(name) @classmethod - def remove_person_attribute(cls, name: str, id: int) -> None: + def remove_person_attribute(cls, name: PersonAttributeType, id: int) -> None: + props = cls.get_owner_props() if name == "MiddleNames": - bpy.context.scene.BIMOwnerProperties.middle_names.remove(id) + props.middle_names.remove(id) elif name == "PrefixTitles": - bpy.context.scene.BIMOwnerProperties.prefix_titles.remove(id) + props.prefix_titles.remove(id) elif name == "SuffixTitles": - bpy.context.scene.BIMOwnerProperties.suffix_titles.remove(id) + props.suffix_titles.remove(id) + else: + assert_never(name) @classmethod def set_role(cls, role: ifcopenshell.entity_instance) -> None: - bpy.context.scene.BIMOwnerProperties.active_role_id = role.id() + props = cls.get_owner_props() + props.active_role_id = role.id() @classmethod def import_role_attributes(cls) -> None: role = cls.get_role() - props = bpy.context.scene.BIMOwnerProperties + props = cls.get_owner_props() props.role_attributes.clear() bonsai.bim.helper.import_attributes("IfcActorRole", props.role_attributes, role.get_info()) @classmethod def clear_role(cls) -> None: - bpy.context.scene.BIMOwnerProperties.active_role_id = 0 + props = cls.get_owner_props() + props.active_role_id = 0 @classmethod def get_role(cls) -> ifcopenshell.entity_instance: - return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_role_id) + props = cls.get_owner_props() + return tool.Ifc().get().by_id(props.active_role_id) @classmethod def export_role_attributes(cls) -> dict[str, Any]: - return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMOwnerProperties.role_attributes) + props = cls.get_owner_props() + return bonsai.bim.helper.export_attributes(props.role_attributes) @classmethod def set_actor(cls, actor: ifcopenshell.entity_instance) -> None: - bpy.context.scene.BIMOwnerProperties.active_actor_id = actor.id() + props = cls.get_owner_props() + props.active_actor_id = actor.id() @classmethod def import_actor_attributes(cls, actor: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMOwnerProperties + props = cls.get_owner_props() props.actor_attributes.clear() bonsai.bim.helper.import_attributes2(actor, props.actor_attributes) @classmethod def clear_actor(cls) -> None: - bpy.context.scene.BIMOwnerProperties.active_actor_id = 0 + props = cls.get_owner_props() + props.active_actor_id = 0 @classmethod def export_actor_attributes(cls) -> dict[str, Any]: - props = bpy.context.scene.BIMOwnerProperties + props = cls.get_owner_props() attributes = bonsai.bim.helper.export_attributes(props.actor_attributes) return attributes @classmethod def get_actor(cls) -> ifcopenshell.entity_instance: - return tool.Ifc().get().by_id(bpy.context.scene.BIMOwnerProperties.active_actor_id) + props = cls.get_owner_props() + return tool.Ifc().get().by_id(props.active_actor_id) diff --git a/src/bonsai/bonsai/tool/sequence.py b/src/bonsai/bonsai/tool/sequence.py index f85d5af909..f89afe3fec 100644 --- a/src/bonsai/bonsai/tool/sequence.py +++ b/src/bonsai/bonsai/tool/sequence.py @@ -339,7 +339,7 @@ class Sequence(bonsai.core.tool.Sequence): @classmethod def get_task_attribute_value(cls, attribute_name: str) -> Any: props = cls.get_work_schedule_props() - return props.task_attributes.get(attribute_name).get_value() + return props.task_attributes[attribute_name].get_value() @classmethod def get_active_task(cls) -> ifcopenshell.entity_instance: diff --git a/src/bonsai/test/tool/test_blender.py b/src/bonsai/test/tool/test_blender.py index 3775bf86c4..c86a4bfd5b 100644 --- a/src/bonsai/test/tool/test_blender.py +++ b/src/bonsai/test/tool/test_blender.py @@ -22,8 +22,12 @@ import bonsai.core.tool import bonsai.tool as tool import pytest from test.bim.bootstrap import NewFile +from typing import TYPE_CHECKING from bonsai.tool.blender import Blender as subject +if TYPE_CHECKING: + import bpy._typing.rna_enums as rna_enums + class TestImplementsTool(NewFile): def test_run(self): @@ -34,6 +38,7 @@ class TestCopyNodeGraph(NewFile): def test_run(self): material_to = bpy.data.materials.new("material_to") material_to.use_nodes = True + assert material_to.node_tree material_to_nodes = material_to.node_tree.nodes assert len(material_to_nodes) == 2 for node in material_to_nodes: @@ -70,7 +75,7 @@ class TestBlenderErrorMessageExtraction(NewFile): bl_idname = "object.test_fail_operator" bl_label = "Test Fail Operator" - def execute(self, context): + def execute(self, context) -> "set[rna_enums.OperatorReturnItems]": self.report({"INFO"}, "Info message.") subject.report_operator_errors(self, ERROR_REPORTS) return {"FINISHED"} diff --git a/src/bonsai/test/tool/test_brick.py b/src/bonsai/test/tool/test_brick.py index 0220cb2962..bdc5e34ddc 100644 --- a/src/bonsai/test/tool/test_brick.py +++ b/src/bonsai/test/tool/test_brick.py @@ -138,6 +138,7 @@ class TestAddBrickifcProject(NewFile): class TestAddBrickifcReference(NewFile): def test_run(self): TestAddBrickifcProject().test_run() + assert BrickStore.graph element = tool.Ifc.get().createIfcChiller(ifcopenshell.guid.new()) element.Name = "Chiller" project = URIRef(f"http://example.org/digitaltwin#{tool.Ifc.get().by_type('IfcProject')[0].GlobalId}") @@ -173,6 +174,7 @@ class TestAddRelation(NewFile): class TestRemoveRelation(NewFile): def test_run(self): TestAddRelation().test_run() + assert BrickStore.graph source, relation, destination = list( BrickStore.graph.triples((None, URIRef("https://brickschema.org/schema/Brick#feeds"), None)) )[0] @@ -321,7 +323,7 @@ class TestGetConvertableBrickSystems(NewFile): class TestGetParentSpace(NewFile): - def test_run(cls): + def test_run(self): ifc = ifcopenshell.file() element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBuildingStorey") subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSpace") @@ -333,17 +335,17 @@ class TestGetParentSpace(NewFile): class TestGetElementContainer(NewFile): - def test_nothing(cls): + def test_nothing(self): pass class TestGetElementSystems(NewFile): - def test_nothing(cls): + def test_nothing(self): pass class TestGetElementFeeds(NewFile): - def test_run(cls): + def test_run(self): pass diff --git a/src/bonsai/test/tool/test_collector.py b/src/bonsai/test/tool/test_collector.py index 8c0cd557c6..ba59aeef92 100644 --- a/src/bonsai/test/tool/test_collector.py +++ b/src/bonsai/test/tool/test_collector.py @@ -20,6 +20,9 @@ import bpy import ifcopenshell import ifcopenshell.api import ifcopenshell.api.aggregate +import ifcopenshell.api.feature +import ifcopenshell.api.group +import ifcopenshell.api.spatial import ifcopenshell.util.element import bonsai.core.tool import bonsai.tool as tool @@ -34,6 +37,7 @@ class TestImplementsTool(NewFile): class TestAssign(NewIfc): def test_walls_are_placed_in_its_spatial_collection(self): + assert bpy.context.scene wall_obj = bpy.data.objects.new("Object", None) wall_element = tool.Ifc.get().createIfcWall() tool.Ifc.link(wall_element, wall_obj) @@ -48,6 +52,7 @@ class TestAssign(NewIfc): assert "IfcSite" in wall_obj.users_collection[0].name def test_walls_are_unsorted_if_not_decomposes(self): + assert bpy.context.scene wall_obj = bpy.data.objects.new("Object", None) wall_element = tool.Ifc.get().createIfcWall() tool.Ifc.link(wall_element, wall_obj) @@ -57,6 +62,7 @@ class TestAssign(NewIfc): assert "Unsorted" in wall_obj.users_collection[0].name def test_spatial_structure_elements_are_placed_in_a_collection_of_the_same_name(self): + assert bpy.context.scene building_obj = bpy.data.objects.new("IfcBuilding/Name", None) building_element = tool.Ifc.get().createIfcBuilding() tool.Ifc.link(building_element, building_obj) @@ -71,6 +77,7 @@ class TestAssign(NewIfc): assert building_obj.users_collection[0].name == building_obj.name def test_spaces_are_special_and_are_placed_in_a_spaces_collection(self): + assert bpy.context.scene space_obj = bpy.data.objects.new("IfcSpace/Name", None) space_element = tool.Ifc.get().createIfcSpace() tool.Ifc.link(space_element, space_obj) @@ -85,6 +92,7 @@ class TestAssign(NewIfc): assert space_obj.users_collection[0].name == "IfcSpace" def test_multiple_assigns_do_not_create_duplicate_spatial_structure_collections(self): + assert bpy.context.scene space_obj = bpy.data.objects.new("IfcSpace/Name", None) space_element = tool.Ifc.get().createIfcSpace() tool.Ifc.link(space_element, space_obj) @@ -102,6 +110,7 @@ class TestAssign(NewIfc): assert not bpy.data.collections.get("IfcSite/My Site.001") def test_spatial_zone_elements_are_not_placed_in_a_collection_of_the_same_name(self): + assert bpy.context.scene space_obj = bpy.data.objects.new("IfcSpaceZone/Name", None) space_element = tool.Ifc.get().createIfcSpatialZone() tool.Ifc.link(space_element, space_obj) @@ -116,6 +125,7 @@ class TestAssign(NewIfc): assert space_obj.users_collection[0].name != space_obj.name def test_aggregates_are_also_placed_in_their_container(self): + assert bpy.context.scene element_obj = bpy.data.objects.new("IfcElementAssembly/Name", None) element = tool.Ifc.get().createIfcElementAssembly() subelement_obj = bpy.data.objects.new("IfcBeam/Name", None) @@ -142,6 +152,7 @@ class TestAssign(NewIfc): assert "IfcSite" in subelement_obj.users_collection[0].name def test_projects_are_placed_in_a_collection_of_the_same_name(self): + assert bpy.context.scene tool.Ifc.set(ifcopenshell.file()) element_obj = bpy.data.objects.new("IfcProject/Name", None) element = tool.Ifc.get().createIfcProject() @@ -152,6 +163,7 @@ class TestAssign(NewIfc): assert element_obj.users_collection[0].name == element_obj.name def test_multiple_assigns_do_not_create_duplicate_collections(self): + assert bpy.context.scene tool.Ifc.set(ifcopenshell.file()) element_obj = bpy.data.objects.new("IfcProject/Name", None) element = tool.Ifc.get().createIfcProject() @@ -163,6 +175,7 @@ class TestAssign(NewIfc): assert not bpy.data.collections.get("IfcProject/Name.001") def test_own_collections_are_retained_and_name_synced(self): + assert bpy.context.scene building_obj = bpy.data.objects.new("IfcBuilding/Name", None) building_element = tool.Ifc.get().createIfcBuilding() tool.Ifc.link(building_element, building_obj) @@ -178,22 +191,24 @@ class TestAssign(NewIfc): ) subject.assign(building_obj) assert bpy.context.scene.collection.children.find(building_collection.name) != -1 - assert bpy.data.collections.get("IfcSite/My Site").children.find(building_collection.name) == -1 - assert bpy.data.collections.get("IfcProject/My Project").children.find(building_collection.name) == -1 + assert bpy.data.collections["IfcSite/My Site"].children.find(building_collection.name) == -1 + assert bpy.data.collections["IfcProject/My Project"].children.find(building_collection.name) == -1 assert bpy.context.scene.collection.children.find(building_collection.name) != -1 assert building_collection.objects.find(building_obj.name) != -1 assert building_collection.name == "IfcBuilding/Name" def test_types_are_placed_in_the_types_collection(self): + assert bpy.context.scene element_obj = bpy.data.objects.new("IfcWallType/Name", None) element = tool.Ifc.get().createIfcWallType() tool.Ifc.link(element, element_obj) bpy.context.scene.collection.objects.link(element_obj) subject.assign(element_obj) assert element_obj.users_collection[0].name == "IfcTypeProduct" - assert bpy.data.collections.get("IfcProject/My Project").children.get("IfcTypeProduct") + assert bpy.data.collections["IfcProject/My Project"].children.get("IfcTypeProduct") def test_openings_are_placed_in_their_voided_elements_container(self): + assert bpy.context.scene element_obj = bpy.data.objects.new("IfcOpeningElement/Name", None) element = tool.Ifc.get().createIfcOpeningElement() tool.Ifc.link(element, element_obj) @@ -210,6 +225,7 @@ class TestAssign(NewIfc): assert element_obj.users_collection[0].name == "IfcSite/My Site" def test_grids_are_placed_in_their_container(self): + assert bpy.context.scene element_obj = bpy.data.objects.new("IfcGrid/Name", None) element = tool.Ifc.get().createIfcGrid() tool.Ifc.link(element, element_obj) @@ -223,6 +239,7 @@ class TestAssign(NewIfc): assert element_obj.users_collection[0].name == "IfcSite/My Site" def test_grids_axes_are_placed_in_the_grids_container(self): + assert bpy.context.scene element_obj = bpy.data.objects.new("IfcGrid/Name", None) axis_obj = bpy.data.objects.new("IfcGrid/Name", None) axis = tool.Ifc.get().createIfcGridAxis() @@ -250,7 +267,7 @@ class TestAssign(NewIfc): subject.assign(element_obj) assert element_obj.users_collection[0].name == "IfcAnnotation/DRAWING" - assert bpy.data.collections.get("IfcProject/My Project").children.get("IfcAnnotation/DRAWING") + assert bpy.data.collections["IfcProject/My Project"].children.get("IfcAnnotation/DRAWING") def test_annotations_are_placed_in_their_drawings_collection(self): self.test_drawings_are_placed_in_their_own_collection() @@ -272,7 +289,7 @@ class TestAssign(NewIfc): tool.Ifc.link(element, element_obj) subject.assign(element_obj) assert element_obj.users_collection[0].name == "IfcStructuralItem" - assert bpy.data.collections.get("IfcProject/My Project").children.get("IfcStructuralItem") + assert bpy.data.collections["IfcProject/My Project"].children.get("IfcStructuralItem") def test_structural_connections_are_placed_in_a_connections_collection(self): element_obj = bpy.data.objects.new("IfcStructuralCurveConnection/Name", None) @@ -280,7 +297,7 @@ class TestAssign(NewIfc): tool.Ifc.link(element, element_obj) subject.assign(element_obj) assert element_obj.users_collection[0].name == "IfcStructuralItem" - assert bpy.data.collections.get("IfcProject/My Project").children.get("IfcStructuralItem") + assert bpy.data.collections["IfcProject/My Project"].children.get("IfcStructuralItem") class TestAssignIFC4X3(NewIfc4X3): @@ -290,7 +307,7 @@ class TestAssignIFC4X3(NewIfc4X3): tool.Ifc.link(element, element_obj) subject.assign(element_obj) assert element_obj.users_collection[0].name == "IfcLinearPositioningElement" - assert bpy.data.collections.get("IfcProject/My Project").children.get("IfcLinearPositioningElement") + assert bpy.data.collections["IfcProject/My Project"].children.get("IfcLinearPositioningElement") def test_referents_are_placed_in_a_special_collection(self): element_obj = bpy.data.objects.new("Name", None) @@ -298,4 +315,4 @@ class TestAssignIFC4X3(NewIfc4X3): tool.Ifc.link(element, element_obj) subject.assign(element_obj) assert element_obj.users_collection[0].name == "IfcReferent" - assert bpy.data.collections.get("IfcProject/My Project").children.get("IfcReferent") + assert bpy.data.collections["IfcProject/My Project"].children.get("IfcReferent") diff --git a/src/bonsai/test/tool/test_context.py b/src/bonsai/test/tool/test_context.py index 6ceb978f9e..7c3c5837d8 100644 --- a/src/bonsai/test/tool/test_context.py +++ b/src/bonsai/test/tool/test_context.py @@ -34,7 +34,8 @@ class TestSetContext(test.bim.bootstrap.NewFile): ifc = ifcopenshell.file() context = ifc.createIfcGeometricRepresentationContext() subject.set_context(context) - assert bpy.context.scene.BIMContextProperties.active_context_id == context.id() + props = subject.get_context_props() + assert props.active_context_id == context.id() class TestImportAttributes(test.bim.bootstrap.NewFile): @@ -48,11 +49,11 @@ class TestImportAttributes(test.bim.bootstrap.NewFile): context.Precision = 1 subject.set_context(context) subject.import_attributes() - props = bpy.context.scene.BIMContextProperties - assert props.context_attributes.get("ContextIdentifier").string_value == "ContextIdentifier" - assert props.context_attributes.get("ContextType").string_value == "ContextType" - assert props.context_attributes.get("CoordinateSpaceDimension").int_value == 1 - assert props.context_attributes.get("Precision").string_value == "1.0" + props = subject.get_context_props() + assert props.context_attributes["ContextIdentifier"].string_value == "ContextIdentifier" + assert props.context_attributes["ContextType"].string_value == "ContextType" + assert props.context_attributes["CoordinateSpaceDimension"].int_value == 1 + assert props.context_attributes["Precision"].string_value == "1.0" def test_importing_a_subcontext(self): ifc = ifcopenshell.file() @@ -63,11 +64,11 @@ class TestImportAttributes(test.bim.bootstrap.NewFile): subcontext.UserDefinedTargetView = "UserDefinedTargetView" subject.set_context(subcontext) subject.import_attributes() - props = bpy.context.scene.BIMContextProperties - assert props.context_attributes.get("TargetScale").float_value == 0.5 - assert props.context_attributes.get("TargetView").enum_value == "NOTDEFINED" - assert props.context_attributes.get("UserDefinedTargetView").string_value == "UserDefinedTargetView" - assert not props.context_attributes.get("Precision") + props = subject.get_context_props() + assert props.context_attributes["TargetScale"].float_value == 0.5 + assert props.context_attributes["TargetView"].enum_value == "NOTDEFINED" + assert props.context_attributes["UserDefinedTargetView"].string_value == "UserDefinedTargetView" + assert not props.context_attributes["Precision"] def test_importing_twice(self): ifc = ifcopenshell.file() @@ -78,15 +79,16 @@ class TestImportAttributes(test.bim.bootstrap.NewFile): subject.import_attributes() context.ContextIdentifier = "ContextIdentifier2" subject.import_attributes() - props = bpy.context.scene.BIMContextProperties - assert props.context_attributes.get("ContextIdentifier").string_value == "ContextIdentifier2" + props = subject.get_context_props() + assert props.context_attributes["ContextIdentifier"].string_value == "ContextIdentifier2" class TestClearContext(test.bim.bootstrap.NewFile): def test_run(self): TestSetContext().test_run() subject.clear_context() - assert bpy.context.scene.BIMContextProperties.active_context_id == 0 + props = subject.get_context_props() + assert props.active_context_id == 0 class TestGetContext(test.bim.bootstrap.NewFile): diff --git a/src/bonsai/test/tool/test_drawing.py b/src/bonsai/test/tool/test_drawing.py index 76292b4efe..b5d7537b0f 100644 --- a/src/bonsai/test/tool/test_drawing.py +++ b/src/bonsai/test/tool/test_drawing.py @@ -21,6 +21,7 @@ from pathlib import Path import bpy import mathutils import ifcopenshell +import ifcopenshell.api.root import ifcopenshell.guid import ifcopenshell.util.element import bonsai.core.tool @@ -68,7 +69,7 @@ class TestCreateCamera(NewFile): class TestCreateSvgSheet(NewFile): def test_run(self): ifc = ifcopenshell.file() - ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject") + ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcProject") tool.Ifc.set(ifc) ifc_path = Path("test/files/temp/test.ifc").absolute() bpy.ops.bim.save_project(filepath=str(ifc_path), should_save_as=True) @@ -92,6 +93,7 @@ class TestDeleteCollection(NewFile): class TestDeleteDrawingElements(NewFile): def test_run(self): + assert bpy.context.scene ifc = ifcopenshell.file() tool.Ifc.set(ifc) obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) @@ -155,13 +157,15 @@ class TestDisableEditingText(NewFile): class TestDisableEditingAssignedProduct(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None) - obj.BIMAssignedProductProperties.is_editing_product = True + props = subject.get_object_assigned_product_props(obj) + props.is_editing_product = True subject.disable_editing_assigned_product(obj) - assert obj.BIMAssignedProductProperties.is_editing_product == False + assert props.is_editing_product == False class TestEnableEditing(NewFile): def test_run(self): + assert bpy.context.scene obj = bpy.data.objects.new("Object", None) bpy.context.scene.collection.objects.link(obj) subject.enable_editing(obj) @@ -211,8 +215,9 @@ class TestEnableEditingText(NewFile): class TestEnableEditingAssignedProduct(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None) + props = subject.get_object_assigned_product_props(obj) subject.enable_editing_assigned_product(obj) - assert obj.BIMAssignedProductProperties.is_editing_product == True + assert props.is_editing_product == True class TestEnsureUniqueDrawingName(NewFile): @@ -314,6 +319,7 @@ class TestGetDocumentUri(NewFile): class TestGetDrawingCollection(NewFile): def test_run(self): + assert bpy.context.scene ifc = ifcopenshell.file() tool.Ifc.set(ifc) obj = bpy.data.objects.new("Object", None) @@ -378,6 +384,7 @@ class TestGenerateDrawingMatrix(NewFile): assert subject.generate_drawing_matrix("PLAN_VIEW", 0) == mathutils.Matrix() def test_creating_a_plan_view_at_the_cursor_at_a_storey(self): + assert bpy.context.scene ifc = ifcopenshell.file() tool.Ifc.set(ifc) obj = bpy.data.objects.new("Object", None) @@ -397,6 +404,7 @@ class TestGenerateDrawingMatrix(NewFile): ) def test_creating_an_rcp_at_the_cursor_at_a_storey(self): + assert bpy.context.scene ifc = ifcopenshell.file() tool.Ifc.set(ifc) obj = bpy.data.objects.new("Object", None) @@ -410,48 +418,56 @@ class TestGenerateDrawingMatrix(NewFile): ) def test_creating_a_north_elevation_at_the_cursor(self): + assert bpy.context.scene bpy.context.scene.cursor.location = (1.0, 2.0, 3.0) assert subject.generate_drawing_matrix("ELEVATION_VIEW", "NORTH") == mathutils.Matrix( ((-1, 0, 0, 1), (0, 0, 1, 2), (0, 1, 0, 3), (0, 0, 0, 1)) ) def test_creating_a_south_elevation_at_the_cursor(self): + assert bpy.context.scene bpy.context.scene.cursor.location = (1.0, 2.0, 3.0) assert subject.generate_drawing_matrix("ELEVATION_VIEW", "SOUTH") == mathutils.Matrix( ((1, 0, 0, 1), (0, 0, -1, 2), (0, 1, 0, 3), (0, 0, 0, 1)) ) def test_creating_an_east_elevation_at_the_cursor(self): + assert bpy.context.scene bpy.context.scene.cursor.location = (1.0, 2.0, 3.0) assert subject.generate_drawing_matrix("ELEVATION_VIEW", "EAST") == mathutils.Matrix( ((0, 0, 1, 1), (1, 0, 0, 2), (0, 1, 0, 3), (0, 0, 0, 1)) ) def test_creating_a_west_elevation_at_the_cursor(self): + assert bpy.context.scene bpy.context.scene.cursor.location = (1.0, 2.0, 3.0) assert subject.generate_drawing_matrix("ELEVATION_VIEW", "WEST") == mathutils.Matrix( ((0, 0, -1, 1), (-1, 0, 0, 2), (0, 1, 0, 3), (0, 0, 0, 1)) ) def test_creating_a_north_section_at_the_cursor(self): + assert bpy.context.scene bpy.context.scene.cursor.location = (1.0, 2.0, 3.0) assert subject.generate_drawing_matrix("SECTION_VIEW", "NORTH") == mathutils.Matrix( ((1, 0, 0, 1), (0, 0, -1, 2), (0, 1, 0, 3), (0, 0, 0, 1)) ) def test_creating_a_south_section_at_the_cursor(self): + assert bpy.context.scene bpy.context.scene.cursor.location = (1.0, 2.0, 3.0) assert subject.generate_drawing_matrix("SECTION_VIEW", "SOUTH") == mathutils.Matrix( ((-1, 0, 0, 1), (0, 0, 1, 2), (0, 1, 0, 3), (0, 0, 0, 1)) ) def test_creating_an_east_section_at_the_cursor(self): + assert bpy.context.scene bpy.context.scene.cursor.location = (1.0, 2.0, 3.0) assert subject.generate_drawing_matrix("SECTION_VIEW", "EAST") == mathutils.Matrix( ((0, 0, -1, 1), (-1, 0, 0, 2), (0, 1, 0, 3), (0, 0, 0, 1)) ) def test_creating_a_west_section_at_the_cursor(self): + assert bpy.context.scene bpy.context.scene.cursor.location = (1.0, 2.0, 3.0) assert subject.generate_drawing_matrix("SECTION_VIEW", "WEST") == mathutils.Matrix( ((0, 0, 1, 1), (1, 0, 0, 2), (0, 1, 0, 3), (0, 0, 0, 1)) @@ -598,9 +614,9 @@ class TestImportTextAttributes(NewFile): subject.import_text_attributes(obj) props = tool.Drawing.get_text_props(obj) literal_props = props.literals[0] - assert literal_props.attributes.get("Literal").string_value == "Literal" - assert literal_props.attributes.get("Path").enum_value == "RIGHT" - assert literal_props.attributes.get("BoxAlignment").string_value == "bottom-left" + assert literal_props.attributes["Literal"].string_value == "Literal" + assert literal_props.attributes["Path"].enum_value == "RIGHT" + assert literal_props.attributes["BoxAlignment"].string_value == "bottom-left" class TestImportAssignedProduct(NewFile): @@ -615,7 +631,8 @@ class TestImportAssignedProduct(NewFile): tool.Ifc.link(wall, wall_obj) tool.Ifc.link(label, label_obj) subject.import_assigned_product(label_obj) - assert label_obj.BIMAssignedProductProperties.relating_product == wall_obj + props = subject.get_object_assigned_product_props(label_obj) + assert props.relating_product == wall_obj def test_doing_nothing_if_no_product_to_import(self): ifc = ifcopenshell.file() @@ -624,7 +641,8 @@ class TestImportAssignedProduct(NewFile): label_obj = bpy.data.objects.new("Object", None) tool.Ifc.link(label, label_obj) subject.import_assigned_product(label_obj) - assert label_obj.BIMAssignedProductProperties.relating_product is None + props = subject.get_object_assigned_product_props(label_obj) + assert props.relating_product is None class TestOpenSchedule(NewFile): diff --git a/src/bonsai/test/tool/test_georeference.py b/src/bonsai/test/tool/test_georeference.py index 96e498482c..84173d79db 100644 --- a/src/bonsai/test/tool/test_georeference.py +++ b/src/bonsai/test/tool/test_georeference.py @@ -59,13 +59,13 @@ class TestImportProjectedCRS(NewFile): projected_crs.MapUnit = unit subject.import_projected_crs() props = tool.Georeference.get_georeference_props() - assert props.projected_crs.get("Name").string_value == "Name" - assert props.projected_crs.get("Description").string_value == "Description" - assert props.projected_crs.get("GeodeticDatum").string_value == "GeodeticDatum" - assert props.projected_crs.get("VerticalDatum").string_value == "VerticalDatum" - assert props.projected_crs.get("MapProjection").string_value == "MapProjection" - assert props.projected_crs.get("MapZone").string_value == "MapZone" - assert props.projected_crs.get("MapUnit").enum_value == str(unit.id()) + assert props.projected_crs["Name"].string_value == "Name" + assert props.projected_crs["Description"].string_value == "Description" + assert props.projected_crs["GeodeticDatum"].string_value == "GeodeticDatum" + assert props.projected_crs["VerticalDatum"].string_value == "VerticalDatum" + assert props.projected_crs["MapProjection"].string_value == "MapProjection" + assert props.projected_crs["MapZone"].string_value == "MapZone" + assert props.projected_crs["MapUnit"].enum_value == str(unit.id()) def test_run_ifc2x3(self): ifc = ifcopenshell.file(schema="IFC2X3") @@ -100,13 +100,13 @@ class TestImportCoordinateOperation(NewFile): map_conversion.Scale = 6 subject.import_coordinate_operation() props = tool.Georeference.get_georeference_props() - assert props.coordinate_operation.get("Eastings").string_value == "1.0" - assert props.coordinate_operation.get("Northings").string_value == "2.0" - assert props.coordinate_operation.get("OrthogonalHeight").string_value == "3.0" + assert props.coordinate_operation["Eastings"].string_value == "1.0" + assert props.coordinate_operation["Northings"].string_value == "2.0" + assert props.coordinate_operation["OrthogonalHeight"].string_value == "3.0" assert props.x_axis_abscissa == "4.0" assert props.x_axis_ordinate == "5.0" assert props.grid_north_angle == "-51.3401917" - assert props.coordinate_operation.get("Scale").string_value == "6.0" + assert props.coordinate_operation["Scale"].string_value == "6.0" def test_run_ifc2x3(self): ifc = ifcopenshell.file(schema="IFC2X3") @@ -220,6 +220,7 @@ class TestGetCursorLocation(NewFile): ifcopenshell.api.run("context.add_context", ifc, context_type="Model") unit = ifcopenshell.api.run("unit.add_si_unit", ifc, unit_type="LENGTHUNIT", prefix="MILLI") ifcopenshell.api.run("unit.assign_unit", ifc, units=[unit]) + assert bpy.context.scene bpy.context.scene.cursor.location = (1.0, 2.0, 3.0) assert subject.get_cursor_location() == [1000.0, 2000.0, 3000.0] @@ -229,7 +230,7 @@ class TestXyz2Enh(NewFile): ifc = ifcopenshell.file() ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject") tool.Ifc.set(ifc) - assert subject.xyz2enh([0.0, 0.0, 0.0]) == (0.0, 0.0, 0.0) + assert subject.xyz2enh((0.0, 0.0, 0.0)) == (0.0, 0.0, 0.0) def test_using_the_blender_offset(self): ifc = ifcopenshell.file() @@ -238,7 +239,7 @@ class TestXyz2Enh(NewFile): props = tool.Georeference.get_georeference_props() props.has_blender_offset = True props.blender_offset_x = "1.0" - assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 0.0, 0.0) + assert subject.xyz2enh((0.0, 0.0, 0.0)) == (1.0, 0.0, 0.0) def test_using_the_map_conversion(self): ifc = ifcopenshell.file() @@ -248,7 +249,7 @@ class TestXyz2Enh(NewFile): ifcopenshell.api.run("georeference.add_georeferencing", ifc) map_conversion = ifc.by_type("IfcMapConversion")[0] map_conversion.Eastings = 1.0 - assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 0.0, 0.0) + assert subject.xyz2enh((0.0, 0.0, 0.0)) == (1.0, 0.0, 0.0) def test_applying_both_blender_offset_and_map_conversion(self): props = tool.Georeference.get_georeference_props() @@ -261,7 +262,7 @@ class TestXyz2Enh(NewFile): ifcopenshell.api.run("georeference.add_georeferencing", ifc) map_conversion = ifc.by_type("IfcMapConversion")[0] map_conversion.Northings = 1.0 - assert subject.xyz2enh([0.0, 0.0, 0.0]) == (1.0, 1.0, 0.0) + assert subject.xyz2enh((0.0, 0.0, 0.0)) == (1.0, 1.0, 0.0) class TestEnh2Xyz(NewFile): @@ -269,7 +270,7 @@ class TestEnh2Xyz(NewFile): ifc = ifcopenshell.file() ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcProject") tool.Ifc.set(ifc) - assert subject.enh2xyz([0.0, 0.0, 0.0]) == (0.0, 0.0, 0.0) + assert subject.enh2xyz((0.0, 0.0, 0.0)) == (0.0, 0.0, 0.0) def test_using_the_blender_offset(self): ifc = ifcopenshell.file() @@ -278,7 +279,7 @@ class TestEnh2Xyz(NewFile): props = tool.Georeference.get_georeference_props() props.has_blender_offset = True props.blender_offset_x = "1.0" - assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, 0.0, 0.0) + assert subject.enh2xyz((0.0, 0.0, 0.0)) == (-1.0, 0.0, 0.0) def test_using_the_map_conversion(self): ifc = ifcopenshell.file() @@ -288,7 +289,7 @@ class TestEnh2Xyz(NewFile): ifcopenshell.api.run("georeference.add_georeferencing", ifc) map_conversion = ifc.by_type("IfcMapConversion")[0] map_conversion.Eastings = 1.0 - assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, 0.0, 0.0) + assert subject.enh2xyz((0.0, 0.0, 0.0)) == (-1.0, 0.0, 0.0) def test_applying_both_blender_offset_and_map_conversion(self): props = tool.Georeference.get_georeference_props() @@ -301,4 +302,4 @@ class TestEnh2Xyz(NewFile): ifcopenshell.api.run("georeference.add_georeferencing", ifc) map_conversion = ifc.by_type("IfcMapConversion")[0] map_conversion.Northings = 1.0 - assert subject.enh2xyz([0.0, 0.0, 0.0]) == (-1.0, -1.0, 0.0) + assert subject.enh2xyz((0.0, 0.0, 0.0)) == (-1.0, -1.0, 0.0) diff --git a/src/bonsai/test/tool/test_library.py b/src/bonsai/test/tool/test_library.py index 12209824ea..64d73f918b 100644 --- a/src/bonsai/test/tool/test_library.py +++ b/src/bonsai/test/tool/test_library.py @@ -79,11 +79,11 @@ class TestImportLibraryAttributes(NewFile): library = ifc.createIfcLibraryInformation("Name", "Version", None, "VersionDate", "Location", "Description") subject.import_library_attributes(library) props = tool.Library.get_library_props() - assert props.library_attributes.get("Name").string_value == "Name" - assert props.library_attributes.get("Version").string_value == "Version" - assert props.library_attributes.get("VersionDate").string_value == "VersionDate" - assert props.library_attributes.get("Location").string_value == "Location" - assert props.library_attributes.get("Description").string_value == "Description" + assert props.library_attributes["Name"].string_value == "Name" + assert props.library_attributes["Version"].string_value == "Version" + assert props.library_attributes["VersionDate"].string_value == "VersionDate" + assert props.library_attributes["Location"].string_value == "Location" + assert props.library_attributes["Description"].string_value == "Description" class TestImportReferenceAttributes(NewFile): @@ -92,11 +92,11 @@ class TestImportReferenceAttributes(NewFile): reference = ifc.createIfcLibraryReference("Location", "Identification", "Name", "Description", "Language") subject.import_reference_attributes(reference) props = tool.Library.get_library_props() - assert props.reference_attributes.get("Location").string_value == "Location" - assert props.reference_attributes.get("Identification").string_value == "Identification" - assert props.reference_attributes.get("Name").string_value == "Name" - assert props.reference_attributes.get("Description").string_value == "Description" - assert props.reference_attributes.get("Language").string_value == "Language" + assert props.reference_attributes["Location"].string_value == "Location" + assert props.reference_attributes["Identification"].string_value == "Identification" + assert props.reference_attributes["Name"].string_value == "Name" + assert props.reference_attributes["Description"].string_value == "Description" + assert props.reference_attributes["Language"].string_value == "Language" class TestImportReferences(NewFile): diff --git a/src/bonsai/test/tool/test_model.py b/src/bonsai/test/tool/test_model.py index b0b7411856..4b539caf97 100644 --- a/src/bonsai/test/tool/test_model.py +++ b/src/bonsai/test/tool/test_model.py @@ -19,7 +19,9 @@ import bpy import ifcopenshell import ifcopenshell.api.material +import ifcopenshell.api.root import ifcopenshell.api.style +import ifcopenshell.api.type import ifcopenshell.util.element import ifcopenshell.util.representation import ifcopenshell.util.unit diff --git a/src/bonsai/test/tool/test_owner.py b/src/bonsai/test/tool/test_owner.py index 8b2219a1c7..d8b60e50b8 100644 --- a/src/bonsai/test/tool/test_owner.py +++ b/src/bonsai/test/tool/test_owner.py @@ -36,7 +36,7 @@ class TestAddAddressAttribute(NewFile): subject().add_address_attribute("FacsimileNumbers") subject().add_address_attribute("ElectronicMailAddresses") subject().add_address_attribute("MessagingIDs") - props = bpy.context.scene.BIMOwnerProperties + props = subject.get_owner_props() assert len(props.address_lines) == 1 assert len(props.telephone_numbers) == 1 assert len(props.facsimile_numbers) == 1 @@ -49,7 +49,7 @@ class TestAddPersonAttribute(NewFile): subject().add_person_attribute("MiddleNames") subject().add_person_attribute("PrefixTitles") subject().add_person_attribute("SuffixTitles") - props = bpy.context.scene.BIMOwnerProperties + props = subject.get_owner_props() assert len(props.middle_names) == 1 assert len(props.prefix_titles) == 1 assert len(props.suffix_titles) == 1 @@ -61,7 +61,8 @@ class TestClearActor(NewFile): actor = ifc.createIfcActor() subject().set_actor(actor) subject().clear_actor() - assert bpy.context.scene.BIMOwnerProperties.active_actor_id == 0 + props = subject.get_owner_props() + assert props.active_actor_id == 0 class TestClearAddress(NewFile): @@ -70,12 +71,13 @@ class TestClearAddress(NewFile): address = ifc.createIfcPostalAddress() subject().set_address(address) subject().clear_address() - assert bpy.context.scene.BIMOwnerProperties.active_address_id == 0 + props = subject.get_owner_props() + assert props.active_address_id == 0 class TestClearOrganisation(NewFile): def test_run(self): - props = bpy.context.scene.BIMOwnerProperties + props = subject.get_owner_props() props.active_organisation_id = 1 subject().clear_organisation() assert props.active_organisation_id == 0 @@ -83,7 +85,7 @@ class TestClearOrganisation(NewFile): class TestClearPerson(NewFile): def test_run(self): - props = bpy.context.scene.BIMOwnerProperties + props = subject.get_owner_props() props.active_person_id = 1 subject().clear_person() assert props.active_person_id == 0 @@ -94,14 +96,16 @@ class TestClearRole(NewFile): role = ifcopenshell.file().createIfcActorRole() subject().set_role(role) subject().clear_role() - assert bpy.context.scene.BIMOwnerProperties.active_role_id == 0 + props = subject.get_owner_props() + assert props.active_role_id == 0 class TestClearUser(NewFile): def test_run(self): TestSetUser().test_run() subject.clear_user() - assert bpy.context.scene.BIMOwnerProperties.active_user_id == 0 + props = subject.get_owner_props() + assert props.active_user_id == 0 class TestExportActorAttributes(NewFile): @@ -288,11 +292,11 @@ class TestImportActorAttributes(NewFile): actor.ObjectType = "ObjectType" subject.set_actor(actor) subject.import_actor_attributes(actor) - props = bpy.context.scene.BIMOwnerProperties - assert props.actor_attributes.get("GlobalId").string_value == "GlobalId" - assert props.actor_attributes.get("Name").string_value == "Name" - assert props.actor_attributes.get("Description").string_value == "Description" - assert props.actor_attributes.get("ObjectType").string_value == "ObjectType" + props = subject.get_owner_props() + assert props.actor_attributes["GlobalId"].string_value == "GlobalId" + assert props.actor_attributes["Name"].string_value == "Name" + assert props.actor_attributes["Description"].string_value == "Description" + assert props.actor_attributes["ObjectType"].string_value == "ObjectType" def test_importing_an_occupant(self): ifc = ifcopenshell.file() @@ -305,12 +309,12 @@ class TestImportActorAttributes(NewFile): actor.PredefinedType = "TENANT" subject.set_actor(actor) subject.import_actor_attributes(actor) - props = bpy.context.scene.BIMOwnerProperties - assert props.actor_attributes.get("GlobalId").string_value == "GlobalId" - assert props.actor_attributes.get("Name").string_value == "Name" - assert props.actor_attributes.get("Description").string_value == "Description" - assert props.actor_attributes.get("ObjectType").string_value == "ObjectType" - assert props.actor_attributes.get("PredefinedType").enum_value == "TENANT" + props = subject.get_owner_props() + assert props.actor_attributes["GlobalId"].string_value == "GlobalId" + assert props.actor_attributes["Name"].string_value == "Name" + assert props.actor_attributes["Description"].string_value == "Description" + assert props.actor_attributes["ObjectType"].string_value == "ObjectType" + assert props.actor_attributes["PredefinedType"].enum_value == "TENANT" class TestImportAddressAttributes(NewFile): @@ -330,16 +334,16 @@ class TestImportAddressAttributes(NewFile): address.Country = "Country" subject().set_address(address) subject().import_address_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.address_attributes.get("Purpose").enum_value == "USERDEFINED" - assert props.address_attributes.get("Description").string_value == "Description" - assert props.address_attributes.get("UserDefinedPurpose").string_value == "UserDefinedPurpose" - assert props.address_attributes.get("InternalLocation").string_value == "InternalLocation" - assert props.address_attributes.get("PostalBox").string_value == "PostalBox" - assert props.address_attributes.get("Town").string_value == "Town" - assert props.address_attributes.get("Region").string_value == "Region" - assert props.address_attributes.get("PostalCode").string_value == "PostalCode" - assert props.address_attributes.get("Country").string_value == "Country" + props = subject.get_owner_props() + assert props.address_attributes["Purpose"].enum_value == "USERDEFINED" + assert props.address_attributes["Description"].string_value == "Description" + assert props.address_attributes["UserDefinedPurpose"].string_value == "UserDefinedPurpose" + assert props.address_attributes["InternalLocation"].string_value == "InternalLocation" + assert props.address_attributes["PostalBox"].string_value == "PostalBox" + assert props.address_attributes["Town"].string_value == "Town" + assert props.address_attributes["Region"].string_value == "Region" + assert props.address_attributes["PostalCode"].string_value == "PostalCode" + assert props.address_attributes["Country"].string_value == "Country" assert len(props.address_lines) == 2 assert props.address_lines[0].name == "Address" assert props.address_lines[1].name == "Lines" @@ -351,8 +355,8 @@ class TestImportAddressAttributes(NewFile): address.Purpose = "OFFICE" subject().set_address(address) subject().import_address_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.address_attributes.get("Purpose").enum_value == "OFFICE" + props = subject.get_owner_props() + assert props.address_attributes["Purpose"].enum_value == "OFFICE" assert len(props.address_lines) == 0 def test_importing_a_telecom_address(self): @@ -370,10 +374,10 @@ class TestImportAddressAttributes(NewFile): address.MessagingIDs = ["Messaging", "IDs"] subject().set_address(address) subject().import_address_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.address_attributes.get("Purpose").enum_value == "USERDEFINED" - assert props.address_attributes.get("Description").string_value == "Description" - assert props.address_attributes.get("UserDefinedPurpose").string_value == "UserDefinedPurpose" + props = subject.get_owner_props() + assert props.address_attributes["Purpose"].enum_value == "USERDEFINED" + assert props.address_attributes["Description"].string_value == "Description" + assert props.address_attributes["UserDefinedPurpose"].string_value == "UserDefinedPurpose" assert [a.name for a in props.telephone_numbers] == ["Telephone", "Numbers"] assert [a.name for a in props.facsimile_numbers] == ["Facsimile", "Numbers"] assert [a.name for a in props.electronic_mail_addresses] == ["Electronic", "Mail", "Addresses"] @@ -386,8 +390,8 @@ class TestImportAddressAttributes(NewFile): address.Purpose = "OFFICE" subject().set_address(address) subject().import_address_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.address_attributes.get("Purpose").enum_value == "OFFICE" + props = subject.get_owner_props() + assert props.address_attributes["Purpose"].enum_value == "OFFICE" assert len(props.telephone_numbers) == 0 assert len(props.facsimile_numbers) == 0 assert len(props.electronic_mail_addresses) == 0 @@ -404,10 +408,10 @@ class TestImportOrganisationAttributes(NewFile): organisation.Description = "Description" subject().set_organisation(organisation) subject().import_organisation_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.organisation_attributes.get("Identification").string_value == "Identification" - assert props.organisation_attributes.get("Name").string_value == "Name" - assert props.organisation_attributes.get("Description").string_value == "Description" + props = subject.get_owner_props() + assert props.organisation_attributes["Identification"].string_value == "Identification" + assert props.organisation_attributes["Name"].string_value == "Name" + assert props.organisation_attributes["Description"].string_value == "Description" def test_overwriting_a_previous_import(self): ifc = ifcopenshell.file() @@ -420,9 +424,9 @@ class TestImportOrganisationAttributes(NewFile): organisation.Identification = "Identification2" organisation.Description = None subject().import_organisation_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.organisation_attributes.get("Identification").string_value == "Identification2" - assert props.organisation_attributes.get("Description").string_value == "" + props = subject.get_owner_props() + assert props.organisation_attributes["Identification"].string_value == "Identification2" + assert props.organisation_attributes["Description"].string_value == "" class TestImportPersonAttributes(NewFile): @@ -438,10 +442,10 @@ class TestImportPersonAttributes(NewFile): person.SuffixTitles = ("suffix", "titles") subject().set_person(person) subject().import_person_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.person_attributes.get("Identification").string_value == "identification" - assert props.person_attributes.get("GivenName").string_value == "given_name" - assert props.person_attributes.get("FamilyName").string_value == "family_name" + props = subject.get_owner_props() + assert props.person_attributes["Identification"].string_value == "identification" + assert props.person_attributes["GivenName"].string_value == "given_name" + assert props.person_attributes["FamilyName"].string_value == "family_name" assert len(props.middle_names) == 2 assert props.middle_names[0].name == "middle" assert props.middle_names[1].name == "names" @@ -463,9 +467,9 @@ class TestImportPersonAttributes(NewFile): person.Identification = "identification2" person.GivenName = None subject().import_person_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.person_attributes.get("Identification").string_value == "identification2" - assert props.person_attributes.get("GivenName").string_value == "" + props = subject.get_owner_props() + assert props.person_attributes["Identification"].string_value == "identification2" + assert props.person_attributes["GivenName"].string_value == "" class TestImportRoleAttributes(NewFile): @@ -478,10 +482,10 @@ class TestImportRoleAttributes(NewFile): role.Description = "Description" subject().set_role(role) subject().import_role_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.role_attributes.get("Role").enum_value == "USERDEFINED" - assert props.role_attributes.get("UserDefinedRole").string_value == "UserDefinedRole" - assert props.role_attributes.get("Description").string_value == "Description" + props = subject.get_owner_props() + assert props.role_attributes["Role"].enum_value == "USERDEFINED" + assert props.role_attributes["UserDefinedRole"].string_value == "UserDefinedRole" + assert props.role_attributes["Description"].string_value == "Description" def test_importing_twice(self): ifc = ifcopenshell.file() @@ -492,8 +496,8 @@ class TestImportRoleAttributes(NewFile): subject().import_role_attributes() role.Role = "ARCHITECT" subject().import_role_attributes() - props = bpy.context.scene.BIMOwnerProperties - assert props.role_attributes.get("Role").enum_value == "ARCHITECT" + props = subject.get_owner_props() + assert props.role_attributes["Role"].enum_value == "ARCHITECT" class TestRemoveAddressAttribute(NewFile): @@ -503,7 +507,7 @@ class TestRemoveAddressAttribute(NewFile): subject().remove_address_attribute("FacsimileNumbers", 0) subject().remove_address_attribute("ElectronicMailAddresses", 0) subject().remove_address_attribute("MessagingIDs", 0) - props = bpy.context.scene.BIMOwnerProperties + props = subject.get_owner_props() assert len(props.address_lines) == 0 assert len(props.telephone_numbers) == 0 assert len(props.facsimile_numbers) == 0 @@ -519,7 +523,7 @@ class TestRemovePersonAttribute(NewFile): subject().remove_person_attribute("PrefixTitles", 0) subject().add_person_attribute("SuffixTitles") subject().remove_person_attribute("SuffixTitles", 0) - props = bpy.context.scene.BIMOwnerProperties + props = subject.get_owner_props() assert len(props.middle_names) == 0 assert len(props.prefix_titles) == 0 assert len(props.suffix_titles) == 0 @@ -530,7 +534,8 @@ class TestSetActor(NewFile): ifc = ifcopenshell.file() actor = ifc.createIfcActor() subject().set_actor(actor) - assert bpy.context.scene.BIMOwnerProperties.active_actor_id == actor.id() + props = subject.get_owner_props() + assert props.active_actor_id == actor.id() class TestSetAddress(NewFile): @@ -538,28 +543,32 @@ class TestSetAddress(NewFile): ifc = ifcopenshell.file() address = ifc.createIfcPostalAddress() subject().set_address(address) - assert bpy.context.scene.BIMOwnerProperties.active_address_id == address.id() + props = subject.get_owner_props() + assert props.active_address_id == address.id() class TestSetOrganisation(NewFile): def test_run(self): organisation = ifcopenshell.file().createIfcOrganization() subject().set_organisation(organisation) - assert bpy.context.scene.BIMOwnerProperties.active_organisation_id == organisation.id() + props = subject.get_owner_props() + assert props.active_organisation_id == organisation.id() class TestSetPerson(NewFile): def test_run(self): person = ifcopenshell.file().createIfcPerson() subject().set_person(person) - assert bpy.context.scene.BIMOwnerProperties.active_person_id == person.id() + props = subject.get_owner_props() + assert props.active_person_id == person.id() class TestSetRole(NewFile): def test_run(self): role = ifcopenshell.file().createIfcActorRole() subject().set_role(role) - assert bpy.context.scene.BIMOwnerProperties.active_role_id == role.id() + props = subject.get_owner_props() + assert props.active_role_id == role.id() class TestSetUser(NewFile): @@ -568,4 +577,5 @@ class TestSetUser(NewFile): tool.Ifc.set(ifc) user = ifc.createIfcPersonAndOrganization() subject.set_user(user) - assert bpy.context.scene.BIMOwnerProperties.active_user_id == user.id() + props = subject.get_owner_props() + assert props.active_user_id == user.id() diff --git a/src/bonsai/test/tool/test_style.py b/src/bonsai/test/tool/test_style.py index 0b38e66a15..ba72c3b5e4 100644 --- a/src/bonsai/test/tool/test_style.py +++ b/src/bonsai/test/tool/test_style.py @@ -391,8 +391,8 @@ class TestImportSurfaceAttributes(NewFile): props = tool.Style.get_style_props() style = ifc.create_entity("IfcSurfaceStyle", "Name", "BOTH") subject.import_surface_attributes(style) - assert props.attributes.get("Name").string_value == "Name" - assert props.attributes.get("Side").enum_value == "BOTH" + assert props.attributes["Name"].string_value == "Name" + assert props.attributes["Side"].enum_value == "BOTH" def test_importing_surface_attributes_twice(self): tool.Ifc.set(ifc := ifcopenshell.file()) @@ -400,12 +400,12 @@ class TestImportSurfaceAttributes(NewFile): props = tool.Style.get_style_props() subject.import_surface_attributes(style) assert len(props.attributes) == 2 - assert props.attributes.get("Name").string_value == "Name" - assert props.attributes.get("Side").enum_value == "BOTH" + assert props.attributes["Name"].string_value == "Name" + assert props.attributes["Side"].enum_value == "BOTH" subject.import_surface_attributes(style) assert len(props.attributes) == 2 - assert props.attributes.get("Name").string_value == "Name" - assert props.attributes.get("Side").enum_value == "BOTH" + assert props.attributes["Name"].string_value == "Name" + assert props.attributes["Side"].enum_value == "BOTH" class TestImportPresentationStyles(NewFile): diff --git a/src/bonsai/test/tool/test_system.py b/src/bonsai/test/tool/test_system.py index 5a6ce1949f..40e54d67b7 100644 --- a/src/bonsai/test/tool/test_system.py +++ b/src/bonsai/test/tool/test_system.py @@ -19,6 +19,7 @@ import bpy import ifcopenshell import ifcopenshell.api +import ifcopenshell.util.unit import bonsai.core.tool import bonsai.tool as tool import numpy as np @@ -43,6 +44,7 @@ class TestAddPorts(NewFile): element = tool.Ifc.get_entity(obj) obj.matrix_world = Euler((pi / 2, 0, pi / 2)).to_matrix().to_4x4() @ obj.matrix_world # move origin + assert isinstance(obj.data, bpy.types.Mesh) for v in obj.data.vertices: v.co += Vector((0, 0, 2.5)) return obj, element @@ -90,6 +92,7 @@ class TestAddPorts(NewFile): class TestCreateEmptyAtCursorWithElementOrientation(NewFile): def test_run(self): + assert bpy.context.scene ifc = ifcopenshell.file() tool.Ifc().set(ifc) obj = bpy.data.objects.new("Object", None) @@ -175,10 +178,10 @@ class TestImportSystemAttributes(NewFile): system.ObjectType = "ObjectType" subject().import_system_attributes(system) props = tool.System.get_system_props() - assert props.system_attributes.get("GlobalId").string_value == "GlobalId" - assert props.system_attributes.get("Name").string_value == "Name" - assert props.system_attributes.get("Description").string_value == "Description" - assert props.system_attributes.get("ObjectType").string_value == "ObjectType" + assert props.system_attributes["GlobalId"].string_value == "GlobalId" + assert props.system_attributes["Name"].string_value == "Name" + assert props.system_attributes["Description"].string_value == "Description" + assert props.system_attributes["ObjectType"].string_value == "ObjectType" def test_importing_a_building_system(self): ifc = ifcopenshell.file() @@ -192,12 +195,12 @@ class TestImportSystemAttributes(NewFile): system.LongName = "LongName" subject().import_system_attributes(system) props = tool.System.get_system_props() - assert props.system_attributes.get("GlobalId").string_value == "GlobalId" - assert props.system_attributes.get("Name").string_value == "Name" - assert props.system_attributes.get("Description").string_value == "Description" - assert props.system_attributes.get("ObjectType").string_value == "ObjectType" - assert props.system_attributes.get("PredefinedType").enum_value == "SHADING" - assert props.system_attributes.get("LongName").string_value == "LongName" + assert props.system_attributes["GlobalId"].string_value == "GlobalId" + assert props.system_attributes["Name"].string_value == "Name" + assert props.system_attributes["Description"].string_value == "Description" + assert props.system_attributes["ObjectType"].string_value == "ObjectType" + assert props.system_attributes["PredefinedType"].enum_value == "SHADING" + assert props.system_attributes["LongName"].string_value == "LongName" def test_importing_a_distribution_system(self): ifc = ifcopenshell.file() @@ -211,12 +214,12 @@ class TestImportSystemAttributes(NewFile): system.LongName = "LongName" subject().import_system_attributes(system) props = tool.System.get_system_props() - assert props.system_attributes.get("GlobalId").string_value == "GlobalId" - assert props.system_attributes.get("Name").string_value == "Name" - assert props.system_attributes.get("Description").string_value == "Description" - assert props.system_attributes.get("ObjectType").string_value == "ObjectType" - assert props.system_attributes.get("PredefinedType").enum_value == "ELECTRICAL" - assert props.system_attributes.get("LongName").string_value == "LongName" + assert props.system_attributes["GlobalId"].string_value == "GlobalId" + assert props.system_attributes["Name"].string_value == "Name" + assert props.system_attributes["Description"].string_value == "Description" + assert props.system_attributes["ObjectType"].string_value == "ObjectType" + assert props.system_attributes["PredefinedType"].enum_value == "ELECTRICAL" + assert props.system_attributes["LongName"].string_value == "LongName" class TestImportSystems(NewFile): @@ -245,7 +248,7 @@ class TestLoadPorts(NewFile): port = ifc.create_entity("IfcDistributionPort") subject.load_ports(element, [port]) obj = tool.Ifc.get_object(port) - assert obj + assert isinstance(obj, bpy.types.Object) assert obj.users_collection assert list(obj.location) == [0, 0, 0] @@ -262,6 +265,7 @@ class TestRunRootAssignClass(NewFile): class TestSelectSystemProducts(NewFile): def test_run(self): + assert bpy.context.scene ifc = ifcopenshell.file() tool.Ifc().set(ifc) element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcPump") diff --git a/src/bonsai/test/tool/test_type.py b/src/bonsai/test/tool/test_type.py index ed434e9cc5..04614986ea 100644 --- a/src/bonsai/test/tool/test_type.py +++ b/src/bonsai/test/tool/test_type.py @@ -18,6 +18,8 @@ import bpy import ifcopenshell +import ifcopenshell.api.root +import ifcopenshell.api.type import bonsai.core.tool import bonsai.tool as tool from test.bim.bootstrap import NewFile @@ -153,9 +155,9 @@ class TestGetTypeOccurrences(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc.set(ifc) - wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType") - wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") - ifcopenshell.api.run("type.assign_type", ifc, related_objects=[wall], relating_type=wall_type) + wall_type = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWallType") + wall = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall") + ifcopenshell.api.type.assign_type(ifc, related_objects=[wall], relating_type=wall_type) assert subject.get_type_occurrences(wall_type) == (wall,) diff --git a/src/bonsai/test/tool/test_unit.py b/src/bonsai/test/tool/test_unit.py index 09db8b72bd..212c72a559 100644 --- a/src/bonsai/test/tool/test_unit.py +++ b/src/bonsai/test/tool/test_unit.py @@ -104,6 +104,7 @@ class TestExportUnitAttributes(NewFile): class TestGetSceneUnitName(NewFile): def test_getting_an_imperial_name(self): + assert bpy.context.scene props = tool.Blender.get_bim_props() bpy.context.scene.unit_settings.system = "IMPERIAL" bpy.context.scene.unit_settings.length_unit = "MILES" @@ -130,12 +131,14 @@ class TestGetSceneUnitName(NewFile): assert subject.get_scene_unit_name("VOLUMEUNIT") == "cubic inch" def test_getting_a_name_with_no_unit_system(self): + assert bpy.context.scene bpy.context.scene.unit_settings.system = "NONE" assert subject.get_scene_unit_name("LENGTHUNIT") == "foot" class TestGetSceneUnitSIPrefix: def test_run(self): + assert bpy.context.scene bpy.context.scene.unit_settings.system = "METRIC" bpy.context.scene.unit_settings.length_unit = "METERS" assert subject.get_scene_unit_si_prefix("LENGTHUNIT") is None @@ -298,6 +301,7 @@ class TestImportUnits(NewFile): class TestIsSceneUnitMetric(NewFile): def test_run(self): + assert bpy.context.scene props = bpy.context.scene.unit_settings props.system = "METRIC" assert subject.is_scene_unit_metric() is True