diff --git a/src/bonsai/bonsai/bim/import_ifc.py b/src/bonsai/bonsai/bim/import_ifc.py index 0251b19c94..b33b0127d5 100644 --- a/src/bonsai/bonsai/bim/import_ifc.py +++ b/src/bonsai/bonsai/bim/import_ifc.py @@ -29,6 +29,7 @@ import numpy.typing as npt import multiprocessing import ifcopenshell import ifcopenshell.geom +import ifcopenshell.ifcopenshell_wrapper as W import ifcopenshell.util.unit import ifcopenshell.util.element import ifcopenshell.util.geolocation @@ -1032,9 +1033,9 @@ class IfcImporter: def create_curve( self, element: ifcopenshell.entity_instance, - shape: Union[ifcopenshell.geom.ShapeElementType, ifcopenshell.geom.ShapeType], + shape: Union[W.Triangulation, W.TriangulationElement], ) -> bpy.types.Curve: - if isinstance(shape, ifcopenshell.geom.ShapeElementType): + if isinstance(shape, W.TriangulationElement): geometry = shape.geometry else: geometry = shape diff --git a/src/bonsai/bonsai/bim/module/bsdd/operator.py b/src/bonsai/bonsai/bim/module/bsdd/operator.py index b9161b3d40..337b242147 100644 --- a/src/bonsai/bonsai/bim/module/bsdd/operator.py +++ b/src/bonsai/bonsai/bim/module/bsdd/operator.py @@ -41,7 +41,7 @@ class SearchBSDDClassifications(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - total = core.search_bsdd_class(tool.Bsdd, context.scene.BIMBSDDProperties.keyword) + total = core.search_bsdd_class(tool.Bsdd, tool.Bsdd.get_bsdd_props().keyword) self.report({"INFO"}, f"{total} bSDD classes found.") return {"FINISHED"} @@ -71,7 +71,7 @@ class SearchBSDDProperties(bpy.types.Operator): def execute(self, context): props = tool.Bsdd.get_bsdd_props() - core.search_bsdd_properties(tool.Bsdd, context.scene.BIMBSDDProperties.keyword, self.obj, self.obj_type) + core.search_bsdd_properties(tool.Bsdd, tool.Bsdd.get_bsdd_props().keyword, self.obj, self.obj_type) self.report({"INFO"}, f"{len(props.properties)} bSDD properties found.") return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/bsdd/prop.py b/src/bonsai/bonsai/bim/module/bsdd/prop.py index bdf84fe8d9..1895750387 100644 --- a/src/bonsai/bonsai/bim/module/bsdd/prop.py +++ b/src/bonsai/bonsai/bim/module/bsdd/prop.py @@ -31,7 +31,7 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) -from typing import Union +from typing import Union, TYPE_CHECKING, Literal def get_active_dictionary(self, context): @@ -54,27 +54,41 @@ def update_active_class_index(self: "BIMBSDDProperties", context: bpy.types.Cont class BSDDDictionary(PropertyGroup): - name: StringProperty(name="Name") uri: StringProperty(name="URI") default_language_code: StringProperty(name="Language") organization_name_owner: StringProperty(name="Organization") status: StringProperty(name="Status") version: StringProperty(name="Version") is_active: BoolProperty( - name="Is Active", description="Enable to search with this dictionary", default=False, update=update_is_active + name="Is Active", + description="Enable to search with this dictionary", + default=False, + update=update_is_active, ) + if TYPE_CHECKING: + uri: str + default_language_code: str + organization_name_owner: str + status: str + version: str + is_active: bool + class BSDDClassification(PropertyGroup): - name: StringProperty(name="Name") reference_code: StringProperty(name="Reference Code") uri: StringProperty(name="URI") dictionary_name: StringProperty(name="Dictionary Name") dictionary_namespace_uri: StringProperty(name="Dictionary Namespace URI") + if TYPE_CHECKING: + reference_code: str + uri: str + dictionary_name: str + dictionary_namespace_uri: str + class BSDDProperty(PropertyGroup): - name: StringProperty(name="Name") code: StringProperty(name="Code") uri: StringProperty(name="URI") pset: StringProperty(name="Pset") @@ -82,11 +96,19 @@ class BSDDProperty(PropertyGroup): name="Is Selected", description="Select to add or edit this property", default=False, update=update_is_selected ) + if TYPE_CHECKING: + code: str + uri: str + pset: str + is_selected: bool + class BSDDPset(PropertyGroup): - name: StringProperty(name="Name") properties: CollectionProperty(name="Properties", type=Attribute) + if TYPE_CHECKING: + properties: bpy.types.bpy_prop_collection_idprop[Attribute] + class BIMBSDDProperties(PropertyGroup): active_dictionary: StringProperty(name="Active Dictionary") @@ -131,6 +153,28 @@ class BIMBSDDProperties(PropertyGroup): ) classification_psets: CollectionProperty(name="Classification Psets", type=BSDDPset) + if TYPE_CHECKING: + active_dictionary: str + active_dictionary: str + active_uri: str + dictionaries: bpy.types.bpy_prop_collection_idprop[BSDDDictionary] + active_dictionary_index: int + classifications: bpy.types.bpy_prop_collection_idprop[BSDDClassification] + active_classification_index: int + property_filter_mode: Literal["CLASS", "KEYWORD"] + classes: bpy.types.bpy_prop_collection_idprop[BSDDClassification] + active_class_index: int + properties: bpy.types.bpy_prop_collection_idprop[BSDDProperty] + active_property_index: int + selected_properties: bpy.types.bpy_prop_collection_idprop[Attribute] + keyword: str + should_filter_ifc_class: bool + use_only_ifc_properties: bool + load_preview_dictionaries: bool + load_inactive_dictionaries: bool + load_test_dictionaries: bool + classification_psets: bpy.types.bpy_prop_collection_idprop[BSDDPset] + @property def active_class(self) -> Union[BSDDClassification, None]: return tool.Blender.get_active_uilist_element(self.classes, self.active_class_index) diff --git a/src/bonsai/bonsai/bim/module/bsdd/ui.py b/src/bonsai/bonsai/bim/module/bsdd/ui.py index 7a8eb7a8c7..638d4d1d52 100644 --- a/src/bonsai/bonsai/bim/module/bsdd/ui.py +++ b/src/bonsai/bonsai/bim/module/bsdd/ui.py @@ -16,9 +16,15 @@ # 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.tool as tool from bonsai.bim.module.bsdd.data import BSDDData from bpy.types import Panel, UIList +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from bonsai.bim.module.bsdd.prop import BIMBSDDProperties, BSDDDictionary, BSDDClassification, BSDDProperty class BIM_PT_bsdd(Panel): @@ -33,7 +39,8 @@ class BIM_PT_bsdd(Panel): def draw(self, context): if not BSDDData.is_loaded: BSDDData.load() - props = context.scene.BIMBSDDProperties + props = tool.Bsdd.get_bsdd_props() + assert self.layout layout = self.layout if len(props.dictionaries): row = self.layout.row() @@ -70,9 +77,17 @@ class BIM_PT_bsdd(Panel): class BIM_UL_bsdd_dictionaries(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: BIMBSDDProperties, + item: BSDDDictionary, + icon, + active_data, + active_propname, + ) -> None: if item: - props = context.scene.BIMBSDDProperties row = layout.row(align=True) if item.status != "Active": row.label( @@ -86,7 +101,16 @@ class BIM_UL_bsdd_dictionaries(UIList): class BIM_UL_bsdd_classifications(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: BIMBSDDProperties, + item: BSDDClassification, + icon, + active_data, + active_propname, + ) -> None: if item: row = layout.row(align=True) row.label(text=item.reference_code) @@ -95,7 +119,16 @@ class BIM_UL_bsdd_classifications(UIList): class BIM_UL_bsdd_classes(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: BIMBSDDProperties, + item: BSDDClassification, + icon, + active_data, + active_propname, + ) -> None: if item: row = layout.row(align=True) row.label(text=item.name) @@ -103,7 +136,16 @@ class BIM_UL_bsdd_classes(UIList): class BIM_UL_bsdd_properties(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: BIMBSDDProperties, + item: BSDDProperty, + icon, + active_data, + active_propname, + ) -> None: if item: row = layout.row(align=True) name = item.name diff --git a/src/bonsai/bonsai/bim/module/classification/data.py b/src/bonsai/bonsai/bim/module/classification/data.py index d47c64881e..86c7d27443 100644 --- a/src/bonsai/bonsai/bim/module/classification/data.py +++ b/src/bonsai/bonsai/bim/module/classification/data.py @@ -83,7 +83,7 @@ class ReferencesData: def active_classification_library(cls): if not IfcStore.classification_file or not IfcStore.classification_file.by_type("IfcClassification"): return False - props = bpy.context.scene.BIMClassificationProperties + props = tool.Classification.get_classification_props() name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name if name in [e.Name for e in tool.Ifc.get().by_type("IfcClassification")]: return name diff --git a/src/bonsai/bonsai/bim/module/classification/operator.py b/src/bonsai/bonsai/bim/module/classification/operator.py index bead1b8d6a..2d7327abb5 100644 --- a/src/bonsai/bonsai/bim/module/classification/operator.py +++ b/src/bonsai/bonsai/bim/module/classification/operator.py @@ -47,7 +47,7 @@ class AddClassification(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - props = context.scene.BIMClassificationProperties + props = tool.Classification.get_classification_props() ifcopenshell.api.classification.add_classification( tool.Ifc.get(), classification=IfcStore.classification_file.by_id(int(props.available_classifications)), @@ -60,7 +60,7 @@ class AddManualClassification(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - props = context.scene.BIMClassificationProperties + props = tool.Classification.get_classification_props() attributes = bonsai.bim.helper.export_attributes(props.classification_attributes) classification = ifcopenshell.api.classification.add_classification(tool.Ifc.get(), classification="Unnamed") ifcopenshell.api.classification.edit_classification( @@ -84,7 +84,7 @@ class AddManualClassificationReference(bpy.types.Operator, tool.Ifc.Operator): objects = [context.active_object.name] else: objects = [self.obj] - props = context.scene.BIMClassificationReferenceProperties + props = tool.Classification.get_classification_reference_props() attributes = bonsai.bim.helper.export_attributes(props.reference_attributes) products = [ tool.Ifc.get().by_id(ifc_definition_id) @@ -110,7 +110,7 @@ class AddClassificationFromBSDD(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - cprops = context.scene.BIMClassificationProperties + cprops = tool.Classification.get_classification_props() bprops = tool.Bsdd.get_bsdd_props() if cprops.classification_source == "BSDD": dictionaries = [d.uri for d in bprops.dictionaries if d.is_active] @@ -142,7 +142,7 @@ class EnableAddingManualClassification(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - props = context.scene.BIMClassificationProperties + props = tool.Classification.get_classification_props() props.is_adding = True props.active_classification_id = 0 props.classification_attributes.clear() @@ -156,7 +156,7 @@ class DisableAddingManualClassification(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - props = context.scene.BIMClassificationProperties + props = tool.Classification.get_classification_props() props.is_adding = False return {"FINISHED"} @@ -167,7 +167,7 @@ class EnableAddingManualClassificationReference(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - props = context.scene.BIMClassificationReferenceProperties + props = tool.Classification.get_classification_reference_props() props.is_adding = True props.reference_attributes.clear() bonsai.bim.helper.import_attributes2("IfcClassificationReference", props.reference_attributes) @@ -180,7 +180,7 @@ class DisableAddingManualClassificationReference(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - props = context.scene.BIMClassificationReferenceProperties + props = tool.Classification.get_classification_reference_props() props.is_adding = False return {"FINISHED"} @@ -203,7 +203,7 @@ class EnableEditingClassification(bpy.types.Operator): new.string_value = "" if new.is_null else json.dumps(data[name]) return True - props = context.scene.BIMClassificationProperties + props = tool.Classification.get_classification_props() props.classification_attributes.clear() bonsai.bim.helper.import_attributes2( tool.Ifc.get().by_id(self.classification), props.classification_attributes, callback @@ -218,7 +218,8 @@ class DisableEditingClassification(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - context.scene.BIMClassificationProperties.active_classification_id = 0 + props = tool.Classification.get_classification_props() + props.active_classification_id = 0 return {"FINISHED"} @@ -246,7 +247,7 @@ class EditClassification(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - props = context.scene.BIMClassificationProperties + props = tool.Classification.get_classification_props() def callback(attributes, prop): if prop.name == "ReferenceTokens": @@ -271,7 +272,7 @@ class EnableEditingClassificationReference(bpy.types.Operator): obj: bpy.props.StringProperty() def execute(self, context): - props = context.scene.BIMClassificationReferenceProperties + props = tool.Classification.get_classification_reference_props() props.reference_attributes.clear() bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.reference), props.reference_attributes) props.active_reference_id = self.reference @@ -285,7 +286,7 @@ class DisableEditingClassificationReference(bpy.types.Operator): obj: bpy.props.StringProperty() def execute(self, context): - context.scene.BIMClassificationReferenceProperties.active_reference_id = 0 + tool.Classification.get_classification_reference_props().active_reference_id = 0 return {"FINISHED"} @@ -337,7 +338,7 @@ class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator): obj: bpy.props.StringProperty() def _execute(self, context): - props = context.scene.BIMClassificationReferenceProperties + props = tool.Classification.get_classification_reference_props() attributes = bonsai.bim.helper.export_attributes(props.reference_attributes) ifc_file = tool.Ifc.get() ifcopenshell.api.classification.edit_reference( @@ -364,7 +365,7 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator): objects = [context.active_object.name] else: objects = [self.obj] - props = context.scene.BIMClassificationProperties + props = tool.Classification.get_classification_props() classification = None classification_name = IfcStore.classification_file.by_id(int(props.available_classifications)).Name for element in tool.Ifc.get().by_type("IfcClassification"): @@ -405,8 +406,7 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator): objects = [context.active_object.name] else: objects = [self.obj] - props = context.scene.BIMClassificationProperties - bprops = context.scene.BIMBSDDProperties + bprops = tool.Bsdd.get_bsdd_props() use_only_ifc_properties: bool = bprops.use_only_ifc_properties bsdd_classification = bprops.classifications[bprops.active_classification_index] @@ -487,7 +487,7 @@ class ChangeClassificationLevel(bpy.types.Operator): parent_id: bpy.props.IntProperty() def execute(self, context): - props = context.scene.BIMClassificationProperties + props = tool.Classification.get_classification_props() props.available_library_references.clear() for reference in IfcStore.classification_file.by_id(self.parent_id).HasReferences: new = props.available_library_references.add() @@ -509,6 +509,6 @@ class DisableEditingClassificationReferences(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - props = context.scene.BIMClassificationProperties + props = tool.Classification.get_classification_props() props.available_library_references.clear() return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/classification/prop.py b/src/bonsai/bonsai/bim/module/classification/prop.py index 0c09799285..c6b12d9893 100644 --- a/src/bonsai/bonsai/bim/module/classification/prop.py +++ b/src/bonsai/bonsai/bim/module/classification/prop.py @@ -17,6 +17,7 @@ # along with Bonsai. If not, see . import bpy +import bonsai.tool as tool from bonsai.bim.prop import Attribute from bonsai.bim.module.classification.data import ClassificationsData, ClassificationReferencesData from bpy.types import PropertyGroup @@ -30,9 +31,12 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) +from typing import TYPE_CHECKING -def get_available_classifications(self, context): +def get_available_classifications( + self: "BIMClassificationProperties", context: bpy.types.Context +) -> tool.Blender.BLENDER_ENUM_ITEMS: if not ClassificationsData.is_loaded: ClassificationsData.load() return ClassificationsData.data["available_classifications"] @@ -44,19 +48,26 @@ def get_classifications(self, context): return ClassificationReferencesData.data["classifications"] -def get_classification_source(self, context): +def get_classification_source( + self: "BIMClassificationProperties", context: bpy.types.Context +) -> tool.Blender.BLENDER_ENUM_ITEMS: if not ClassificationsData.is_loaded: ClassificationsData.load() return ClassificationsData.data["classification_source"] class ClassificationReference(PropertyGroup): - name: StringProperty(name="Name") identification: StringProperty(name="Identification") ifc_definition_id: IntProperty(name="IFC Definition ID") has_references: BoolProperty(name="Has References") referenced_source: IntProperty(name="IFC Definition ID") + if TYPE_CHECKING: + identification: str + ifc_definition_id: int + has_references: bool + referenced_source: int + class BIMClassificationProperties(PropertyGroup): is_adding: BoolProperty(name="Is Adding", default=False) @@ -68,9 +79,25 @@ class BIMClassificationProperties(PropertyGroup): active_library_referenced_source: IntProperty(name="Active Library Referenced Source") active_library_reference_index: IntProperty(name="Active Library Reference Index") + if TYPE_CHECKING: + is_adding: bool + classification_source: str + available_classifications: str + classification_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + active_classification_id: int + available_library_references: bpy.types.bpy_prop_collection_idprop[ClassificationReference] + active_library_referenced_source: int + active_library_reference_index: int + class BIMClassificationReferenceProperties(PropertyGroup): is_adding: BoolProperty(name="Is Adding", default=False) classifications: EnumProperty(items=get_classifications, name="Classifications") reference_attributes: CollectionProperty(name="Reference Attributes", type=Attribute) active_reference_id: IntProperty(name="Active Reference Id") + + if TYPE_CHECKING: + is_adding: bool + classifications: str + reference_attributes: bpy.types.bpy_prop_collection_idprop + active_reference_id: int diff --git a/src/bonsai/bonsai/bim/module/classification/ui.py b/src/bonsai/bonsai/bim/module/classification/ui.py index fc808a206b..e2a27edbfe 100644 --- a/src/bonsai/bonsai/bim/module/classification/ui.py +++ b/src/bonsai/bonsai/bim/module/classification/ui.py @@ -16,6 +16,7 @@ # 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 @@ -27,6 +28,10 @@ from bonsai.bim.module.classification.data import ( MaterialClassificationsData, CostClassificationsData, ) +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from bonsai.bim.module.classification.prop import BIMClassificationProperties, ClassificationReference class BIM_PT_classifications(Panel): @@ -46,7 +51,8 @@ class BIM_PT_classifications(Panel): if not ClassificationsData.is_loaded: ClassificationsData.load() - self.props = context.scene.BIMClassificationProperties + self.props = tool.Classification.get_classification_props() + assert self.layout row = self.layout.row(align=True) row.label(text="Source", icon="OUTLINER") @@ -110,9 +116,9 @@ class ReferenceUI: def draw_ui(self, context): obj = context.active_object - self.sprops = context.scene.BIMClassificationProperties - self.bprops = context.scene.BIMBSDDProperties - self.props = context.scene.BIMClassificationReferenceProperties + self.sprops = tool.Classification.get_classification_props() + self.bprops = tool.Bsdd.get_bsdd_props() + self.props = tool.Classification.get_classification_reference_props() self.file = tool.Ifc.get() self.draw_add_ui(context) @@ -315,7 +321,16 @@ class BIM_PT_cost_classifications(Panel, ReferenceUI): class BIM_UL_classifications(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: BIMClassificationProperties, + item: ClassificationReference, + icon, + active_data, + active_propname, + ): if item: if item.has_references: op = layout.operator("bim.change_classification_level", text="", icon="DISCLOSURE_TRI_RIGHT") diff --git a/src/bonsai/bonsai/bim/module/pset/ui.py b/src/bonsai/bonsai/bim/module/pset/ui.py index 9f559b4556..b119a40749 100644 --- a/src/bonsai/bonsai/bim/module/pset/ui.py +++ b/src/bonsai/bonsai/bim/module/pset/ui.py @@ -255,7 +255,7 @@ class BIM_PT_object_psets(Panel): ObjectPsetsData.load() props = context.active_object.PsetProperties - self.bprops = context.scene.BIMBSDDProperties + self.bprops = tool.Bsdd.get_bsdd_props() row = self.layout.row(align=True) prop_with_search(row, props, "pset_name", text="") if props.pset_name != "BBIM_BSDD" and not props.pset_name.startswith(tool.Bsdd.identifier_url): diff --git a/src/bonsai/bonsai/core/bsdd.py b/src/bonsai/bonsai/core/bsdd.py index 5c16b7e9ec..34ff455091 100644 --- a/src/bonsai/bonsai/core/bsdd.py +++ b/src/bonsai/bonsai/core/bsdd.py @@ -26,20 +26,20 @@ if TYPE_CHECKING: import bonsai.tool as tool -def import_bsdd_classes(bsdd: tool.Bsdd, obj, obj_type) -> int: +def import_bsdd_classes(bsdd: type[tool.Bsdd], obj, obj_type) -> int: return bsdd.import_classes(obj, obj_type) -def search_bsdd_properties(bsdd: tool.Bsdd, keyword: str, obj, obj_type) -> int: +def search_bsdd_properties(bsdd: type[tool.Bsdd], keyword: str, obj, obj_type) -> int: return bsdd.import_properties(obj, obj_type, keyword) -def load_bsdd(bsdd: tool.Bsdd) -> None: +def load_bsdd(bsdd: type[tool.Bsdd]) -> None: bsdd.clear_dictionaries() bsdd.create_dictionaries(bsdd.get_dictionaries()) -def search_bsdd_class(bsdd: tool.Bsdd, keyword: str) -> int: +def search_bsdd_class(bsdd: type[tool.Bsdd], keyword: str) -> int: bsdd.clear_classes() related_entities = bsdd.get_related_ifc_entities() return bsdd.search_class(keyword, related_entities) diff --git a/src/bonsai/bonsai/core/model.py b/src/bonsai/bonsai/core/model.py index 5372e662b2..c82de50e0e 100644 --- a/src/bonsai/bonsai/core/model.py +++ b/src/bonsai/bonsai/core/model.py @@ -32,7 +32,11 @@ if TYPE_CHECKING: def unjoin_walls( - ifc: tool.Ifc, blender: tool.Blender, geometry: tool.Geometry, joiner: DumbWallJoiner, model: tool.Model + ifc: type[tool.Ifc], + blender: type[tool.Blender], + geometry: type[tool.Geometry], + joiner: DumbWallJoiner, + model: type[tool.Model], ) -> None: """Unjoin selected walls.""" for obj in blender.get_selected_objects(): @@ -45,11 +49,11 @@ def unjoin_walls( def extend_walls( - ifc: tool.Ifc, - blender: tool.Blender, - geometry: tool.Geometry, + ifc: type[tool.Ifc], + blender: type[tool.Blender], + geometry: type[tool.Geometry], joiner: DumbWallJoiner, - model: tool.Model, + model: type[tool.Model], target: Vector, connection: Optional[str] = None, ) -> None: @@ -62,11 +66,11 @@ def extend_walls( def join_walls_LV( - ifc: tool.Ifc, - blender: tool.Blender, - geometry: tool.Geometry, + ifc: type[tool.Ifc], + blender: type[tool.Blender], + geometry: type[tool.Geometry], joiner: DumbWallJoiner, - model: tool.Model, + model: type[tool.Model], join_type: Literal["L", "V"] = "L", ) -> None: selected_objs = [ @@ -86,7 +90,7 @@ def join_walls_LV( joiner.connect(another_selected_object, active_obj) -def offset_walls(ifc: tool.Ifc, blender: tool.Blender, model: tool.Model, offset_type: OffsetType): +def offset_walls(ifc: type[tool.Ifc], blender: type[tool.Blender], model: type[tool.Model], offset_type: OffsetType): objs = [ obj for obj in blender.get_selected_objects() @@ -98,7 +102,11 @@ def offset_walls(ifc: tool.Ifc, blender: tool.Blender, model: tool.Model, offset def align_walls( - ifc: tool.Ifc, blender: tool.Blender, model: tool.Model, aligner: DumbWallAligner, align_type: AlignType + ifc: type[tool.Ifc], + blender: type[tool.Blender], + model: type[tool.Model], + aligner: DumbWallAligner, + align_type: AlignType, ): reference_obj = blender.get_active_object(is_selected=True) if not (e := ifc.get_entity(reference_obj) or not model.get_usage_type(e) == "LAYER2"): @@ -122,7 +130,9 @@ def align_walls( aligner.align_last_layer(obj) -def align_objects(blender: tool.Blender, model: tool.Model, align_type: Literal["CENTER", "POSITIVE", "NEGATIVE"]): +def align_objects( + blender: type[tool.Blender], model: type[tool.Model], align_type: Literal["CENTER", "POSITIVE", "NEGATIVE"] +): reference_obj = blender.get_active_object(is_selected=True) objs = [o for o in blender.get_selected_objects() if o != reference_obj] if not reference_obj or not objs: @@ -131,9 +141,9 @@ def align_objects(blender: tool.Blender, model: tool.Model, align_type: Literal[ def extend_wall_to_slab( - ifc: tool.Ifc, - geometry: tool.Geometry, - model: tool.Model, + ifc: type[tool.Ifc], + geometry: type[tool.Geometry], + model: type[tool.Model], slab_obj: bpy.types.Object, wall_objs: list[bpy.types.Object], ) -> None: @@ -150,7 +160,11 @@ def extend_wall_to_slab( def join_walls_TZ( - ifc: tool.Ifc, blender: tool.Blender, geometry: tool.Geometry, joiner: DumbWallJoiner, model: tool.Model + ifc: type[tool.Ifc], + blender: type[tool.Blender], + geometry: type[tool.Geometry], + joiner: DumbWallJoiner, + model: type[tool.Model], ) -> None: selected_objs = [ o diff --git a/src/bonsai/bonsai/tool/bsdd.py b/src/bonsai/bonsai/tool/bsdd.py index 5ee004327d..7f090546b5 100644 --- a/src/bonsai/bonsai/tool/bsdd.py +++ b/src/bonsai/bonsai/tool/bsdd.py @@ -1,3 +1,22 @@ +# Bonsai - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of Bonsai. +# +# Bonsai is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Bonsai is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Bonsai. If not, see . + +from __future__ import annotations import bonsai.core.tool import bonsai.tool as tool import bpy @@ -8,6 +27,9 @@ import ifcopenshell.util.element import ifcopenshell.util.classification from typing import Any, Union, Optional, TYPE_CHECKING +if TYPE_CHECKING: + from bonsai.bim.module.bsdd.prop import BIMBSDDProperties + class Bsdd(bonsai.core.tool.Bsdd): identifier_url = "https://identifier.buildingsmart.org" @@ -16,28 +38,29 @@ class Bsdd(bonsai.core.tool.Bsdd): bsdd_properties: dict[str, dict] = {} @classmethod - def get_bsdd_props(cls): - return bpy.context.scene.BIMBSDDProperties + def get_bsdd_props(cls) -> BIMBSDDProperties: + assert (scene := bpy.context.scene) + return scene.BIMBSDDProperties # pyright: ignore[reportAttributeAccessIssue] @classmethod def clear_class_psets(cls) -> None: - bpy.context.scene.BIMBSDDProperties.classification_psets.clear() + cls.get_bsdd_props().classification_psets.clear() @classmethod def clear_classes(cls) -> None: - bpy.context.scene.BIMBSDDProperties.classifications.clear() + cls.get_bsdd_props().classifications.clear() @classmethod def clear_properties(cls) -> None: - bpy.context.scene.BIMBSDDProperties.properties.clear() + cls.get_bsdd_props().properties.clear() @classmethod def clear_dictionaries(cls) -> None: - bpy.context.scene.BIMBSDDProperties.dictionaries.clear() + cls.get_bsdd_props().dictionaries.clear() @classmethod def create_class_psets(cls, pset_dict: dict[str, dict[str, Any]]) -> None: - props = bpy.context.scene.BIMBSDDProperties + props = cls.get_bsdd_props() data_type_map = { "String": "string", "Real": "float", @@ -60,7 +83,7 @@ class Bsdd(bonsai.core.tool.Bsdd): @classmethod def create_dictionaries(cls, dictionaries: list[bsdd.DictionaryContractV1]) -> None: - props = bpy.context.scene.BIMBSDDProperties + props = cls.get_bsdd_props() for dictionary in sorted(dictionaries, key=lambda d: d["name"]): new = props.dictionaries.add() new.name = dictionary["name"] @@ -72,7 +95,7 @@ class Bsdd(bonsai.core.tool.Bsdd): @classmethod def get_active_class_data(cls) -> Union[bsdd.ClassContractV1, dict]: - prop = bpy.context.scene.BIMBSDDProperties + prop = cls.get_bsdd_props() bsdd_classification = prop.classifications[prop.active_classification_index] if not bsdd_classification: return {} @@ -80,18 +103,18 @@ class Bsdd(bonsai.core.tool.Bsdd): @classmethod def get_active_dictionary_uri(cls) -> str: - return bpy.context.scene.BIMBSDDProperties.active_uri + return cls.get_bsdd_props().active_uri @classmethod def get_dictionary(cls, uri: str) -> bsdd.DictionaryContractV1: - props = bpy.context.scene.BIMBSDDProperties + props = cls.get_bsdd_props() response = cls.client.get_dictionary(dictionary_uri=uri, include_test_dictionaries=props.load_test_dictionaries) if dicts := response.get("dictionaries"): return dicts[0] @classmethod def get_dictionaries(cls) -> list[bsdd.DictionaryContractV1]: - props = bpy.context.scene.BIMBSDDProperties + props = cls.get_bsdd_props() response = cls.client.get_dictionary(include_test_dictionaries=props.load_test_dictionaries) dicts = response.get("dictionaries") or [] statuses = ["Active"] @@ -165,7 +188,7 @@ class Bsdd(bonsai.core.tool.Bsdd): limit: int = 100, should_paginate: bool = True, ): - cprops = bpy.context.scene.BIMClassificationProperties + cprops = tool.Classification.get_classification_props() bprops = cls.get_bsdd_props() dictionary_uris = ( [d.uri for d in bprops.dictionaries if d.is_active] @@ -202,13 +225,13 @@ class Bsdd(bonsai.core.tool.Bsdd): @classmethod def set_active_bsdd(cls, name: str, uri: str) -> None: - props = bpy.context.scene.BIMBSDDProperties + props = cls.get_bsdd_props() props.active_dictionary = name props.active_uri = uri @classmethod def should_filter_ifc_class(cls) -> bool: - return bpy.context.scene.BIMBSDDProperties.should_filter_ifc_class + return cls.get_bsdd_props().should_filter_ifc_class @classmethod def get_bsdd_class(cls, uri: str) -> dict: diff --git a/src/bonsai/bonsai/tool/classification.py b/src/bonsai/bonsai/tool/classification.py index 113c69b480..3535e0a12f 100644 --- a/src/bonsai/bonsai/tool/classification.py +++ b/src/bonsai/bonsai/tool/classification.py @@ -22,10 +22,23 @@ import ifcopenshell.api import ifcopenshell.util.classification import bonsai.core.tool import bonsai.tool as tool -from typing import Union, assert_never +from typing import Union, assert_never, TYPE_CHECKING + +if TYPE_CHECKING: + from bonsai.bim.module.classification.prop import BIMClassificationReferenceProperties, BIMClassificationProperties class Classification(bonsai.core.tool.Classification): + @classmethod + def get_classification_props(cls) -> BIMClassificationProperties: + assert (scene := bpy.context.scene) + return scene.BIMClassificationProperties # pyright: ignore[reportAttributeAccessIssue] + + @classmethod + def get_classification_reference_props(cls) -> BIMClassificationReferenceProperties: + assert (scene := bpy.context.scene) + return scene.BIMClassificationReferenceProperties # pyright: ignore[reportAttributeAccessIssue] + @classmethod def get_location(cls, classification: ifcopenshell.entity_instance) -> Union[str, None]: schema = classification.file.schema diff --git a/src/bonsai/test/tool/test_classification.py b/src/bonsai/test/tool/test_classification.py index c2f299a059..7e9bd3d5de 100644 --- a/src/bonsai/test/tool/test_classification.py +++ b/src/bonsai/test/tool/test_classification.py @@ -46,8 +46,8 @@ class TestAddClassificationReferenceFromBSDD(NewFile): bpy.ops.bim.load_bsdd_domains() uri = "https://identifier.buildingsmart.org/uri/molio/cciconstruction/1.0" bpy.ops.bim.set_active_bsdd_domain(name="CCI Construction", uri=uri) - props = context.scene.BIMBSDDProperties - bpy.context.scene.BIMClassificationProperties.classification_source = "BSDD" + props = tool.Bsdd.get_bsdd_props() + tool.Classification.get_classification_props().classification_source = "BSDD" props.should_filter_ifc_class = True props.keyword = "Room" bpy.ops.bim.search_bsdd_classifications() @@ -70,8 +70,8 @@ class TestAddClassificationReferenceFromBSDD(NewFile): bpy.ops.bim.load_bsdd_domains() uri = "https://identifier.buildingsmart.org/uri/molio/cciconstruction/1.0" bpy.ops.bim.set_active_bsdd_domain(name="CCI Construction", uri=uri) - props = context.scene.BIMBSDDProperties - bpy.context.scene.BIMClassificationProperties.classification_source = "BSDD" + props = tool.Bsdd.get_bsdd_props() + tool.Classification.get_classification_props().classification_source = "BSDD" props.should_filter_ifc_class = True props.keyword = "Room" bpy.ops.bim.search_bsdd_classifications() @@ -103,12 +103,12 @@ class TestAddClassificationReferenceFromBSDD(NewFile): element = tool.Ifc.get_entity(obj) assert element - props = context.scene.BIMBSDDProperties + props = tool.Bsdd.get_bsdd_props() props.load_preview_domains = True bpy.ops.bim.load_bsdd_domains() uri = "https://identifier.buildingsmart.org/uri/ifcairport/ifcairport/0.9" bpy.ops.bim.set_active_bsdd_domain(name="IFC Airport", uri=uri) - bpy.context.scene.BIMClassificationProperties.classification_source = "BSDD" + tool.Classification.get_classification_props().classification_source = "BSDD" props.should_filter_ifc_class = False # Important due to class mismatch. props.keyword = "check-in conveyor" bpy.ops.bim.search_bsdd_classifications() diff --git a/src/bonsai/test/tool/test_loader.py b/src/bonsai/test/tool/test_loader.py index 9845f50342..e0c5766421 100644 --- a/src/bonsai/test/tool/test_loader.py +++ b/src/bonsai/test/tool/test_loader.py @@ -540,7 +540,7 @@ class TestSetupActiveBsddClassification(NewFile): filepath = "test/files/temp/test.ifc" ifc_file.write(filepath) bpy.ops.bim.load_project(filepath=filepath) - props = bpy.context.scene.BIMBSDDProperties + props = tool.Bsdd.get_bsdd_props() assert props.active_domain == name assert props.active_uri == base_uri diff --git a/src/ifcopenshell-python/ifcopenshell/validate.py b/src/ifcopenshell-python/ifcopenshell/validate.py index 330bf0b226..2eb29c431a 100644 --- a/src/ifcopenshell-python/ifcopenshell/validate.py +++ b/src/ifcopenshell-python/ifcopenshell/validate.py @@ -351,19 +351,18 @@ def log_internal_cpp_errors(f: ifcopenshell.file, filename: str, logger: Logger) logger.error(m) -entity_attribute_map: dict[tuple[str, str], tuple[entity_type, tuple[attribute]]] = {} +entity_attribute_map: dict[tuple[str, str], tuple[entity_type, tuple[attribute, ...]]] = {} -def get_entity_attributes(schema: schema_definition, entity: str) -> tuple[entity_type, tuple[attribute]]: +def get_entity_attributes(schema: schema_definition, entity: str) -> tuple[entity_type, tuple[attribute, ...]]: cache_key = schema.name(), entity from_cache = entity_attribute_map.get(cache_key) if from_cache: return from_cache - entity_attrs = ( - ent := schema.declaration_by_name(entity), - ent.all_attributes(), - ) + ent = schema.declaration_by_name(entity).as_entity() + assert ent + entity_attrs = (ent, ent.all_attributes()) entity_attribute_map[cache_key] = entity_attrs return entity_attrs diff --git a/src/ifcpatch/ifcpatch/recipes/MergeStyles.py b/src/ifcpatch/ifcpatch/recipes/MergeStyles.py index cb490e77c7..cb2780a360 100644 --- a/src/ifcpatch/ifcpatch/recipes/MergeStyles.py +++ b/src/ifcpatch/ifcpatch/recipes/MergeStyles.py @@ -17,12 +17,14 @@ # along with IfcPatch. If not, see . import ifcopenshell -import ifcopenshell.guid import ifcopenshell.util.element +import ifcpatch +import logging +from typing import Union -class Patcher: - def __init__(self, file, logger): +class Patcher(ifcpatch.BasePatcher): + def __init__(self, file: ifcopenshell.file, logger: Union[logging.Logger, None] = None): """Merge identical styles together Some software may create an obscene number of styles instead of reusing @@ -35,8 +37,7 @@ class Patcher: ifcpatch.execute({"file": model, "recipe": "MergeStyles", "arguments": []}) """ - self.file = file - self.logger = logger + super().__init__(file, logger) def patch(self): for ifc_class in ("IfcColourRgb", "IfcSurfaceStyleShading", "IfcPresentationStyle"):