diff --git a/src/bonsai/bonsai/bim/handler.py b/src/bonsai/bonsai/bim/handler.py index e958098c16..9cea2cca2e 100644 --- a/src/bonsai/bonsai/bim/handler.py +++ b/src/bonsai/bonsai/bim/handler.py @@ -289,7 +289,7 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None def viewport_shading_changed_callback(area: bpy.types.Area) -> None: shading = area.spaces.active.shading.type if shading == "RENDERED": - bpy.context.scene.BIMStylesProperties.active_style_type = "External" + tool.Style.get_style_props().active_style_type = "Internal" def subscribe_to_viewport_shading_changes(): diff --git a/src/bonsai/bonsai/bim/module/cad/workspace.py b/src/bonsai/bonsai/bim/module/cad/workspace.py index 0571d0c776..07d6a597c5 100644 --- a/src/bonsai/bonsai/bim/module/cad/workspace.py +++ b/src/bonsai/bonsai/bim/module/cad/workspace.py @@ -311,7 +311,9 @@ class CadHotkey(bpy.types.Operator): bpy.ops.bim.reset_vertex() -def add_header_apply_button(layout, text, apply_operator, cancel_operator, ui_context=""): +def add_header_apply_button( + layout: bpy.types.UILayout, text: str, apply_operator: str, cancel_operator: str, ui_context: str = "" +) -> None: custom_icon = custom_icon_previews.get(text.upper().replace(" ", "_"), custom_icon_previews["IFC"]).icon_id row = layout.row(align=True) row.label(text=f"{text} Mode", icon_value=custom_icon) diff --git a/src/bonsai/bonsai/bim/module/debug/operator.py b/src/bonsai/bonsai/bim/module/debug/operator.py index 8fa39b1470..c68bedbeb0 100644 --- a/src/bonsai/bonsai/bim/module/debug/operator.py +++ b/src/bonsai/bonsai/bim/module/debug/operator.py @@ -704,13 +704,13 @@ class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator): if purged == 0: return - scene = context.scene if object_type == "PROFILE": props = tool.Profile.get_profile_props() if props.is_editing: bpy.ops.bim.load_profiles() elif object_type == "STYLE": - if scene.BIMStylesProperties.is_editing: + props = tool.Style.get_style_props() + if props.is_editing: bpy.ops.bim.load_styles() elif object_type == "MATERIAL": props = tool.Material.get_material_props() @@ -754,13 +754,13 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator): if merged == 0: return - scene = context.scene if object_type == "PROFILE": props = tool.Profile.get_profile_props() if props.is_editing: bpy.ops.bim.load_profiles() elif object_type == "STYLE": - if scene.BIMStylesProperties.is_editing: + props = tool.Style.get_style_props() + if props.is_editing: bpy.ops.bim.load_styles() elif object_type == "MATERIAL": props = tool.Material.get_material_props() diff --git a/src/bonsai/bonsai/bim/module/material/operator.py b/src/bonsai/bonsai/bim/module/material/operator.py index 3e9ff20924..80f6aafffc 100644 --- a/src/bonsai/bonsai/bim/module/material/operator.py +++ b/src/bonsai/bonsai/bim/module/material/operator.py @@ -576,30 +576,30 @@ class EditAssignedMaterial(bpy.types.Operator, tool.Ifc.Operator): material_set = self.file.by_id(self.material_set) attributes = bonsai.bim.helper.export_attributes(props.material_set_attributes) - ifcopenshell.api.run( - "material.edit_assigned_material", + ifcopenshell.api.material.edit_assigned_material( self.file, - **{"element": material_set, "attributes": attributes}, + element=material_set, + attributes=attributes, ) if self.material_set_usage: material_set_usage = self.file.by_id(self.material_set_usage) attributes = bonsai.bim.helper.export_attributes(props.material_set_usage_attributes) if material_set_usage.is_a("IfcMaterialLayerSetUsage"): - ifcopenshell.api.run( - "material.edit_layer_usage", + ifcopenshell.api.material.edit_layer_usage( self.file, - **{"usage": material_set_usage, "attributes": attributes}, + usage=material_set_usage, + attributes=attributes, ) slab.DumbSlabPlaner().regenerate_from_layer_set(material_set_usage.ForLayerSet) wall.DumbWallPlaner().regenerate_from_layer_set(material_set_usage.ForLayerSet) elif material_set_usage.is_a("IfcMaterialProfileSetUsage"): if attributes.get("CardinalPoint", None): attributes["CardinalPoint"] = int(attributes["CardinalPoint"]) - ifcopenshell.api.run( - "material.edit_profile_usage", + ifcopenshell.api.material.edit_profile_usage( self.file, - **{"usage": material_set_usage, "attributes": attributes}, + usage=material_set_usage, + attributes=attributes, ) bpy.ops.bim.disable_editing_assigned_material(obj=active_obj.name) diff --git a/src/bonsai/bonsai/bim/module/model/prop.py b/src/bonsai/bonsai/bim/module/model/prop.py index a8e486c2a6..d02adfcf8d 100644 --- a/src/bonsai/bonsai/bim/module/model/prop.py +++ b/src/bonsai/bonsai/bim/module/model/prop.py @@ -28,7 +28,7 @@ from math import pi, radians from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDecorator from bonsai.bim.module.model.door import update_door_modifier_bmesh from bonsai.bim.module.model.window import update_window_modifier_bmesh -from typing import TYPE_CHECKING, Literal, get_args, Union, get_args +from typing import TYPE_CHECKING, Literal, get_args, Union, get_args, Any, Optional def get_ifc_class(self: "BIMModelProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]: @@ -607,7 +607,7 @@ class BIMWindowProperties(PropertyGroup): framing_material: str glazing_material: str - def get_general_kwargs(self, convert_to_project_units=False): + def get_general_kwargs(self, convert_to_project_units: bool = False) -> dict[str, Any]: kwargs = { "window_type": self.window_type, "overall_height": self.overall_height, @@ -617,7 +617,9 @@ class BIMWindowProperties(PropertyGroup): return kwargs return tool.Model.convert_data_to_project_units(kwargs, ["window_type"]) - def get_lining_kwargs(self, window_type=None, convert_to_project_units=False): + def get_lining_kwargs( + self, window_type: Optional[WindowType] = None, convert_to_project_units: bool = False + ) -> dict[str, Any]: if not window_type: window_type = self.window_type kwargs = { @@ -660,7 +662,7 @@ class BIMWindowProperties(PropertyGroup): return kwargs return tool.Model.convert_data_to_project_units(kwargs) - def get_panel_kwargs(self, convert_to_project_units=False): + def get_panel_kwargs(self, convert_to_project_units: bool = False) -> dict[str, Any]: kwargs = { "frame_depth": self.frame_depth, "frame_thickness": self.frame_thickness, @@ -669,7 +671,7 @@ class BIMWindowProperties(PropertyGroup): return kwargs return tool.Model.convert_data_to_project_units(kwargs) - def set_props_kwargs_from_ifc_data(self, kwargs): + def set_props_kwargs_from_ifc_data(self, kwargs: dict[str, Any]): kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props) for prop_name in kwargs: setattr(self, prop_name, kwargs[prop_name]) diff --git a/src/bonsai/bonsai/bim/module/model/window.py b/src/bonsai/bonsai/bim/module/model/window.py index e005a1b3ac..41b767d111 100644 --- a/src/bonsai/bonsai/bim/module/model/window.py +++ b/src/bonsai/bonsai/bim/module/model/window.py @@ -28,6 +28,7 @@ import bonsai.core.root import bonsai.core.geometry from ifcopenshell.api.geometry.add_window_representation import DEFAULT_PANEL_SCHEMAS import ifcopenshell.api +import ifcopenshell.api.material import ifcopenshell.util.element import ifcopenshell.util.representation import ifcopenshell.util.shape_builder diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index acc363bf16..eb49a01535 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -65,6 +65,9 @@ from bonsai.bim.module.model.decorator import PolylineDecorator from bonsai.bim.module.model.polyline import PolylineOperator from typing import Union, TYPE_CHECKING, Literal, get_args +if TYPE_CHECKING: + from bonsai.bim.module.project.prop import Link + class NewProject(bpy.types.Operator): bl_idname = "bim.new_project" @@ -1484,7 +1487,7 @@ class ToggleLinkVisibility(bpy.types.Operator): self.toggle_visibility(link) return {"FINISHED"} - def toggle_wireframe(self, link): + def toggle_wireframe(self, link: "Link") -> None: for collection in self.get_linked_collections(): objs = filter(lambda obj: "IfcOpeningElement" not in obj.name, collection.all_objects) for i, obj in enumerate(objs): @@ -1496,7 +1499,7 @@ class ToggleLinkVisibility(bpy.types.Operator): obj.display_type = display_type link.is_wireframe = display_type == "WIRE" - def toggle_visibility(self, link): + def toggle_visibility(self, link: "Link") -> None: linked_collections = self.get_linked_collections() link.is_hidden = (is_hidden := not link.is_hidden) diff --git a/src/bonsai/bonsai/bim/module/style/operator.py b/src/bonsai/bonsai/bim/module/style/operator.py index 5f2d6d2a52..cf5e0a9b66 100644 --- a/src/bonsai/bonsai/bim/module/style/operator.py +++ b/src/bonsai/bonsai/bim/module/style/operator.py @@ -224,7 +224,8 @@ class UpdateCurrentStyle(bpy.types.Operator): current_style_type = material.BIMStyleProperties.active_style_type if self.update_all: - context.scene.BIMStylesProperties.active_style_type = current_style_type + sprops = tool.Style.get_style_props() + sprops.active_style_type = current_style_type return {"FINISHED"} updated_materials = set() @@ -258,8 +259,9 @@ class SetAssetMaterialToExternalStyle(bpy.types.Operator): # the temp override to copy material node tree `right now` name = context.asset.name filepath = context.asset.full_library_path + props = tool.Style.get_style_props() bpy.app.timers.register( - lambda: self.execute_delayed(name, filepath, context.scene.BIMStylesProperties), + lambda: self.execute_delayed(name, filepath, props), first_interval=0.001, ) return {"FINISHED"} @@ -388,7 +390,8 @@ class BrowseExternalStyle(bpy.types.Operator): bpy.data.materials.remove(db["data_block"]) filepath = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path) - attributes = context.scene.BIMStylesProperties.external_style_attributes + props = tool.Style.get_style_props() + attributes = props.external_style_attributes attributes["Location"].string_value = filepath attributes["Identification"].string_value = f"{self.data_block_type}/{self.data_block}" attributes["Name"].string_value = self.data_block @@ -416,7 +419,7 @@ class ActivateExternalStyle(bpy.types.Operator): self.report({"INFO"}, "Material '{self.material_name}' is not an IFC style.") return {"CANCELLED"} - props = context.scene.BIMStylesProperties + props = tool.Style.get_style_props() if props.is_editing_style == style.id() and props.is_editing_class == "IfcExternallyDefinedSurfaceStyle": location = props.external_style_attributes["Location"].string_value identification = props.external_style_attributes["Identification"].string_value @@ -509,7 +512,8 @@ class LoadStyles(bpy.types.Operator): style_type: bpy.props.StringProperty() def execute(self, context): - style_type = self.style_type if self.style_type else context.scene.BIMStylesProperties.style_type + props = tool.Style.get_style_props() + style_type = self.style_type if self.style_type else props.style_type core.load_styles(tool.Style, style_type=style_type) bonsai.bim.handler.refresh_ui_data() return {"FINISHED"} @@ -561,7 +565,8 @@ class ChooseTextureMapPath(bpy.types.Operator): return {"CANCELLED"} filepath = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path) - texture = context.scene.BIMStylesProperties.textures[self.texture_map_index] + props = tool.Style.get_style_props() + texture = props.textures[self.texture_map_index] texture.path = filepath return {"FINISHED"} @@ -577,7 +582,7 @@ class RemoveTextureMap(bpy.types.Operator): self.report({"ERROR"}, "Provide a texture map index") return {"CANCELLED"} - props = context.scene.BIMStylesProperties + props = tool.Style.get_style_props() props.textures.remove(self.texture_map_index) # just to trigger shader graph update props.surface_colour = props.surface_colour @@ -612,7 +617,8 @@ class DuplicateStyle(bpy.types.Operator, tool.Ifc.Operator): style: bpy.props.IntProperty(name="Style ID") def _execute(self, context): - style_type = context.scene.BIMStylesProperties.style_type + props = tool.Style.get_style_props() + style_type = props.style_type ifc_file = tool.Ifc.get() style = ifc_file.by_id(self.style) tool.Style.duplicate_style(style) @@ -636,7 +642,7 @@ class AddPresentationStyle(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() if props.style_type == "IfcSurfaceStyle": style = ifcopenshell.api.run("style.add_style", tool.Ifc.get(), name=props.style_name) @@ -692,7 +698,7 @@ class EnableEditingSurfaceStyle(bpy.types.Operator): ifc_class: bpy.props.StringProperty(default="") def execute(self, context): - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() style = tool.Ifc.get().by_id(self.style) style_elements = tool.Style.get_style_elements(style) @@ -753,7 +759,7 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator): surface_style: Union[ifcopenshell.entity_instance, None] def _execute(self, context): - self.props = bpy.context.scene.BIMStylesProperties + self.props = tool.Style.get_style_props() self.style = tool.Ifc.get().by_id(self.props.is_editing_style) style_elements = tool.Style.get_style_elements(self.style) @@ -950,13 +956,14 @@ class AddSurfaceTexture(bpy.types.Operator): @classmethod def poll(cls, context): - if len(context.scene.BIMStylesProperties.textures) >= 8: + props = tool.Style.get_style_props() + if len(props.textures) >= 8: cls.poll_message_set("Only 8 texture maps available") return False return True def execute(self, context): - props = context.scene.BIMStylesProperties + props = tool.Style.get_style_props() props.textures.add() return {"FINISHED"} @@ -1183,7 +1190,7 @@ class SelectStyleInStylesUI(bpy.types.Operator): style_id: bpy.props.IntProperty() def execute(self, context): - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() ifc_file = tool.Ifc.get() style = ifc_file.by_id(self.style_id) core.load_styles(tool.Style, style.is_a()) @@ -1204,7 +1211,7 @@ class RemoveSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): ifc_file = tool.Ifc.get() - props = context.scene.BIMStylesProperties + props = tool.Style.get_style_props() style = ifc_file.by_id(props.is_editing_style) surface_style = tool.Style.get_style_elements(style)[props.is_editing_class] ifcopenshell.api.style.remove_surface_style(ifc_file, surface_style) diff --git a/src/bonsai/bonsai/bim/module/style/prop.py b/src/bonsai/bonsai/bim/module/style/prop.py index 9111514d46..040db82f60 100644 --- a/src/bonsai/bonsai/bim/module/style/prop.py +++ b/src/bonsai/bonsai/bim/module/style/prop.py @@ -33,7 +33,7 @@ from bpy.props import ( ) import gettext -from typing import Literal, Union +from typing import Literal, Union, TYPE_CHECKING, get_args _ = gettext.gettext @@ -154,6 +154,17 @@ class ColourRgb(PropertyGroup): return "color_value" +SurfaceStyleClass = Literal[ + "IfcSurfaceStyleShading", + "IfcSurfaceStyleRendering", + "IfcSurfaceStyleWithTextures", + "IfcSurfaceStyleLighting", + "IfcSurfaceStyleRefraction", + "IfcExternallyDefinedSurfaceStyle", +] +ColourClass = Literal["IfcColourRgb", "IfcNormalisedRatioMeasure"] + + class BIMStylesProperties(PropertyGroup): is_adding: BoolProperty(name="Is Adding", description="Is adding new IfcPresentationStyle") is_editing: BoolProperty(name="Is Editing", description="Is editing IfcPresentationStyle") @@ -170,17 +181,7 @@ class BIMStylesProperties(PropertyGroup): style_type: EnumProperty(items=get_style_types, default=2, name="Style Type") style_name: StringProperty(name="Style Name") surface_style_class: EnumProperty( - items=[ - (x, x, "") - for x in ( - "IfcSurfaceStyleShading", - "IfcSurfaceStyleRendering", - "IfcSurfaceStyleWithTextures", - "IfcSurfaceStyleLighting", - "IfcSurfaceStyleRefraction", - "IfcExternallyDefinedSurfaceStyle", - ) - ], + items=[(x, x, "") for x in get_args(SurfaceStyleClass)], name="Surface Style Class", default="IfcSurfaceStyleShading", ) @@ -200,7 +201,7 @@ class BIMStylesProperties(PropertyGroup): # TODO: do something on null? is_diffuse_colour_null: BoolProperty(name="Is Null") diffuse_colour_class: EnumProperty( - items=[(x, x, "") for x in ("IfcColourRgb", "IfcNormalisedRatioMeasure")], + items=[(x, x, "") for x in get_args(ColourClass)], name="Diffuse Colour Class", update=update_shader_graph, ) @@ -212,7 +213,7 @@ class BIMStylesProperties(PropertyGroup): ) is_specular_colour_null: BoolProperty(name="Is Null") specular_colour_class: EnumProperty( - items=[(x, x, "") for x in ("IfcColourRgb", "IfcNormalisedRatioMeasure")], + items=[(x, x, "") for x in get_args(ColourClass)], name="Specular Colour Class", update=update_shader_graph, default="IfcNormalisedRatioMeasure", @@ -269,11 +270,49 @@ class BIMStylesProperties(PropertyGroup): active_style_type: EnumProperty( name="Active Style Type", description="Update current blender material to match style type for all objects in the scene", - items=STYLE_TYPES, + items=[(i, i, "") for i in get_args(tool.Style.StyleType)], default="Shading", update=update_shading_styles, ) + if TYPE_CHECKING: + is_adding: bool + is_editing: bool + is_editing_style: int + is_editing_class: str + is_editing_existing_style: bool + attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + external_style_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + refraction_style_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] + lighting_style_colours: bpy.types.bpy_prop_collection_idprop[ColourRgb] + style_type: str + style_name: str + surface_style_class: SurfaceStyleClass + update_graph: bool + + # Shading props. + surface_colour: tuple[float, float, float] + transparency: float + is_diffuse_colour_null: bool + diffuse_colour_class: ColourClass + diffuse_colour: tuple[float, float, float] + diffuse_colour_ratio: float + is_specular_colour_null: bool + specular_colour_class: ColourClass + specular_colour: tuple[float, float, float] + specular_colour_ratio: float + is_specular_highlight_null: bool + specular_highlight: float + reflectance_method: str + + # Texture props. + textures: bpy.types.bpy_prop_collection_idprop[Texture] + uv_mode: Literal["UV", "Generated", "Camera"] + + styles: bpy.types.bpy_prop_collection_idprop[Style] + active_style_index: int + active_style_type: tool.Style.StyleType + def update_shading_style(self: "BIMStyleProperties", context: bpy.types.Context) -> None: blender_material = self.id_data @@ -290,8 +329,13 @@ class BIMStyleProperties(PropertyGroup): active_style_type: EnumProperty( name="Active Style Type", description="Update current blender material to match style type", - items=STYLE_TYPES, + items=[(i, i, "") for i in get_args(tool.Style.StyleType)], default="Shading", update=update_shading_style, ) is_renaming: BoolProperty(description="Used to prevent triggering handler callback.", default=False) + + if TYPE_CHECKING: + ifc_definition_id: int + active_style_type: tool.Style.StyleType + is_renaming: bool diff --git a/src/bonsai/bonsai/bim/module/style/ui.py b/src/bonsai/bonsai/bim/module/style/ui.py index 2b7eaad760..4cdf8aa88d 100644 --- a/src/bonsai/bonsai/bim/module/style/ui.py +++ b/src/bonsai/bonsai/bim/module/style/ui.py @@ -40,7 +40,7 @@ class BIM_PT_styles(Panel): if not StylesData.is_loaded: StylesData.load() - self.props = context.scene.BIMStylesProperties + self.props = tool.Style.get_style_props() if not self.props.is_editing: row = self.layout.row(align=True) @@ -258,7 +258,7 @@ class BIM_UL_styles(UIList): def draw_item(self, context, layout: bpy.types.UILayout, data, item, icon, active_data, active_property): if item: row = layout.row(align=True) - props = context.scene.BIMStylesProperties + props = tool.Style.get_style_props() if item.ifc_definition_id == props.is_editing_style: row.label(text="", icon="GREASEPENCIL") row.prop(item, "name", text="", emboss=False) @@ -330,6 +330,6 @@ def draw_asset_browser_context_menu_append(self, context): asset = context.asset if not asset or not asset.id_type == "MATERIAL": return - if not context.scene.BIMStylesProperties.is_editing: + if not tool.Style.get_style_props().is_editing: return self.layout.operator("bim.set_asset_material_to_external_style") diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 27cb0180bf..dedcbe8b82 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -49,7 +49,7 @@ from bonsai.bim.module.geometry.helper import Helper from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData from bonsai.bim.module.model.opening import FilledOpeningGenerator from ifcopenshell.util.shape_builder import ShapeBuilder -from typing import Optional, Union, TypeVar, Any, Iterable, Literal, TYPE_CHECKING +from typing import Optional, Union, TypeVar, Any, Iterable, Literal, TYPE_CHECKING, Sequence T = TypeVar("T") V_ = tool.Blender.V_ @@ -108,7 +108,7 @@ class Model(bonsai.core.tool.Model): return value * cls.unit_scale @classmethod - def convert_data_to_project_units(cls, data: dict[str, Any], non_si_props: list[str] = []) -> dict[str, Any]: + def convert_data_to_project_units(cls, data: dict[str, Any], non_si_props: Sequence[str] = ()) -> dict[str, Any]: si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) for prop_name in data: if prop_name in non_si_props: @@ -121,7 +121,7 @@ class Model(bonsai.core.tool.Model): return data @classmethod - def convert_data_to_si_units(cls, data: dict[str, Any], non_si_props: list[str] = []) -> dict[str, Any]: + def convert_data_to_si_units(cls, data: dict[str, Any], non_si_props: Sequence[str] = ()) -> dict[str, Any]: si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) for prop_name in data: if prop_name in non_si_props: diff --git a/src/bonsai/bonsai/tool/style.py b/src/bonsai/bonsai/tool/style.py index c41669fa81..b7a7cb7241 100644 --- a/src/bonsai/bonsai/tool/style.py +++ b/src/bonsai/bonsai/tool/style.py @@ -16,9 +16,11 @@ # 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 numpy as np import ifcopenshell +import ifcopenshell.api.style import ifcopenshell.util.element import ifcopenshell.util.representation import bonsai.core.style @@ -26,7 +28,10 @@ import bonsai.core.tool import bonsai.tool as tool import bonsai.bim.helper from mathutils import Color -from typing import Union, Any, Optional, Literal +from typing import Union, Any, Optional, Literal, TYPE_CHECKING + +if TYPE_CHECKING: + from bonsai.bim.module.style.prop import BIMStylesProperties, BIMStyleProperties # fmt: off TEXTURE_MAPS_BY_METHODS = { @@ -44,10 +49,18 @@ STYLE_PROPS_MAP = { "specular_colour": "SpecularColour", } -STYLE_TYPES = Literal["Shading", "External"] - class Style(bonsai.core.tool.Style): + StyleType = Literal["Shading", "External"] + + @classmethod + def get_style_props(cls) -> BIMStylesProperties: + return bpy.context.scene.BIMStylesProperties + + @classmethod + def get_material_style_props(cls, material: bpy.types.Material) -> BIMStyleProperties: + return material.BIMStyleProperties + @classmethod def can_support_rendering_style(cls, obj: bpy.types.Material) -> bool: return obj.use_nodes and hasattr(obj.node_tree, "nodes") @@ -58,19 +71,19 @@ class Style(bonsai.core.tool.Style): @classmethod def enable_adding_presentation_style(cls) -> None: - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() props.is_adding = True props.update_graph = False @classmethod def disable_adding_presentation_style(cls) -> None: - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() props.is_adding = False props.update_graph = True @classmethod def disable_editing(cls) -> None: - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() props.is_editing_style = 0 props.is_editing_class = "" props.attributes.clear() @@ -81,7 +94,7 @@ class Style(bonsai.core.tool.Style): @classmethod def disable_editing_styles(cls) -> None: - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() props.is_editing = False props.styles.clear() @@ -100,27 +113,29 @@ class Style(bonsai.core.tool.Style): @classmethod def enable_editing(cls, style: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() props.is_editing_style = style.id() props.is_editing_class = "IfcSurfaceStyle" @classmethod def enable_editing_styles(cls) -> None: - bpy.context.scene.BIMStylesProperties.is_editing = True + props = cls.get_style_props() + props.is_editing = True @classmethod def export_surface_attributes(cls) -> dict[str, Any]: - props = bpy.context.scene.BIMStylesProperties + props = cls.get_style_props() return bonsai.bim.helper.export_attributes(props.attributes) @classmethod def get_active_style_in_ui(cls) -> Union[bpy.types.PropertyGroup, None]: - props = bpy.context.scene.BIMStylesProperties + props = cls.get_style_props() return props.active_style @classmethod def get_active_style_type(cls) -> str: - return bpy.context.scene.BIMStylesProperties.style_type + props = cls.get_style_props() + return props.style_type @classmethod def get_context(cls) -> Union[ifcopenshell.entity_instance, None]: @@ -136,7 +151,7 @@ class Style(bonsai.core.tool.Style): @classmethod def get_currently_edited_material(cls) -> bpy.types.Material: - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() style = tool.Ifc.get().by_id(props.is_editing_style) obj = tool.Ifc.get_object(style) assert isinstance(obj, bpy.types.Material) @@ -162,7 +177,7 @@ class Style(bonsai.core.tool.Style): """returns style data from blender props in similar way to `Loader.surface_style_to_dict` to be compatible with `Loader.create_surface_style_rendering`""" surface_style_data = dict() - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() available_props = props.bl_rna.properties.keys() for prop_blender, prop_ifc in STYLE_PROPS_MAP.items(): @@ -189,7 +204,7 @@ class Style(bonsai.core.tool.Style): def get_texture_style_data_from_props(cls) -> list[dict[str, Any]]: """returns style data from blender props in similar way to `Loader.surface_texture_to_dict` to be compatible with `Loader.create_surface_style_with_textures`""" - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() textures = [] for texture in props.textures: @@ -210,7 +225,7 @@ class Style(bonsai.core.tool.Style): """set blender style props based on currently edited IfcSurfaceStyle, reset unrelated props to default values""" - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() style = tool.Ifc.get().by_id(props.is_editing_style) # make sure won't be updating while we changing it prev_update_graph_value = props.update_graph @@ -503,7 +518,7 @@ class Style(bonsai.core.tool.Style): @classmethod def get_style_ui_props_attributes(cls, style_type: str) -> Union[bpy.types.PropertyGroup, None]: - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() if style_type == "IfcExternallyDefinedSurfaceStyle": return props.external_style_attributes elif style_type == "IfcSurfaceStyleRefraction": @@ -514,7 +529,7 @@ class Style(bonsai.core.tool.Style): @classmethod def import_presentation_styles(cls, style_type: str) -> None: color_to_tuple = lambda x: (x.Red, x.Green, x.Blue) - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() props.styles.clear() styles = sorted(tool.Ifc.get().by_type(style_type), key=lambda x: x.Name or "Unnamed") for style in styles: @@ -537,7 +552,8 @@ class Style(bonsai.core.tool.Style): @classmethod def import_surface_attributes(cls, style: ifcopenshell.entity_instance) -> None: - attributes = bpy.context.scene.BIMStylesProperties.attributes + props = cls.get_style_props() + attributes = props.attributes attributes.clear() bonsai.bim.helper.import_attributes2(style, attributes) @@ -548,11 +564,13 @@ class Style(bonsai.core.tool.Style): @classmethod def is_editing_styles(cls) -> bool: - return bpy.context.scene.BIMStylesProperties.is_editing + props = cls.get_style_props() + return props.is_editing @classmethod def is_editing_style(cls) -> bool: - return bpy.context.scene.BIMStylesProperties.is_editing_style + props = cls.get_style_props() + return bool(props.is_editing_style) @classmethod def select_elements(cls, elements: list[ifcopenshell.entity_instance]) -> None: @@ -617,7 +635,7 @@ class Style(bonsai.core.tool.Style): blender_material.BIMStyleProperties.active_style_type = blender_material.BIMStyleProperties.active_style_type @classmethod - def switch_shading(cls, blender_material: bpy.types.Material, style_type: STYLE_TYPES) -> None: + def switch_shading(cls, blender_material: bpy.types.Material, style_type: StyleType) -> None: if style_type == "External": try: bpy.ops.bim.activate_external_style(material_name=blender_material.name) diff --git a/src/bonsai/test/tool/test_model.py b/src/bonsai/test/tool/test_model.py index c93532ab25..afcf0e6b5a 100644 --- a/src/bonsai/test/tool/test_model.py +++ b/src/bonsai/test/tool/test_model.py @@ -484,20 +484,21 @@ class TestApplyIfcMaterialChanges(NewFile): red_material = ifcopenshell.api.material.add_material(ifc_file, "Red Material") bpy.ops.bim.load_styles(style_type="IfcSurfaceStyle") bpy.ops.bim.enable_adding_presentation_style() - bpy.data.scenes["Scene"].BIMStylesProperties.style_name = "Red" + sprops = tool.Style.get_style_props() + sprops.style_name = "Red" bpy.ops.bim.add_presentation_style() red_style = next((i for i in ifc_file.by_type("IfcSurfaceStyle") if i.Name == "Red")) ifcopenshell.api.style.assign_material_style(ifc_file, red_material, red_style, context) blue_material = ifcopenshell.api.material.add_material(ifc_file, "Blue Material") bpy.ops.bim.enable_adding_presentation_style() - bpy.data.scenes["Scene"].BIMStylesProperties.style_name = "Blue" + sprops.style_name = "Blue" bpy.ops.bim.add_presentation_style() blue_style = next((i for i in ifc_file.by_type("IfcSurfaceStyle") if i.Name == "Blue")) ifcopenshell.api.style.assign_material_style(ifc_file, blue_material, blue_style, context) bpy.ops.bim.enable_adding_presentation_style() - bpy.data.scenes["Scene"].BIMStylesProperties.style_name = "Green" + sprops.style_name = "Green" bpy.ops.bim.add_presentation_style() if and_elements: diff --git a/src/bonsai/test/tool/test_style.py b/src/bonsai/test/tool/test_style.py index cf46d8fab7..6af83895a3 100644 --- a/src/bonsai/test/tool/test_style.py +++ b/src/bonsai/test/tool/test_style.py @@ -47,7 +47,7 @@ class TestCanSupportRenderingStyle(NewFile): class TestDisableEditing(NewFile): def test_run(self): - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() props.is_editing_style = 1 subject.disable_editing() assert props.is_editing_style == 0 @@ -55,14 +55,15 @@ class TestDisableEditing(NewFile): class TestDisableEditingStyles(NewFile): def test_run(self): - bpy.context.scene.BIMStylesProperties.is_editing = True + props = tool.Style.get_style_props() + props.is_editing = True subject.disable_editing_styles() - assert bpy.context.scene.BIMStylesProperties.is_editing is False + assert props.is_editing is False class TestEnableEditing(NewFile): def test_run(self): - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() style = ifcopenshell.file().create_entity("IfcSurfaceStyle") subject.enable_editing(style) assert props.is_editing_style is style.id() @@ -70,9 +71,10 @@ class TestEnableEditing(NewFile): class TestEnableEditingStyles(NewFile): def test_run(self): - bpy.context.scene.BIMStylesProperties.is_editing = False + props = props = tool.Style.get_style_props() + props.is_editing = False subject.enable_editing_styles() - assert bpy.context.scene.BIMStylesProperties.is_editing is True + assert props.is_editing is True class TestExportSurfaceAttributes(NewFile): @@ -85,9 +87,10 @@ class TestGetActiveStyleType(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc.set(ifc) - bpy.context.scene.BIMStylesProperties.style_type = "IfcSurfaceStyle" + props = tool.Style.get_style_props() + props.style_type = "IfcSurfaceStyle" assert subject.get_active_style_type() == "IfcSurfaceStyle" - bpy.context.scene.BIMStylesProperties.style_type = "IfcCurveStyle" + props.style_type = "IfcCurveStyle" assert subject.get_active_style_type() == "IfcCurveStyle" @@ -378,7 +381,7 @@ class TestGetUVMaps(NewFile): class TestImportSurfaceAttributes(NewFile): def test_run(self): tool.Ifc.set(ifc := ifcopenshell.file()) - props = bpy.context.scene.BIMStylesProperties + 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" @@ -387,7 +390,7 @@ class TestImportSurfaceAttributes(NewFile): def test_importing_surface_attributes_twice(self): tool.Ifc.set(ifc := ifcopenshell.file()) style = ifc.create_entity("IfcSurfaceStyle", "Name", "BOTH") - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() subject.import_surface_attributes(style) assert len(props.attributes) == 2 assert props.attributes.get("Name").string_value == "Name" @@ -404,7 +407,7 @@ class TestImportPresentationStyles(NewFile): tool.Ifc.set(ifc) style = ifc.createIfcCurveStyle(Name="Name") subject.import_presentation_styles("IfcCurveStyle") - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() assert props.styles[0].ifc_definition_id == style.id() assert props.styles[0].name == "Name" assert props.styles[0].total_elements == 0 @@ -414,7 +417,7 @@ class TestImportPresentationStyles(NewFile): tool.Ifc.set(ifc) style = ifc.createIfcFillAreaStyle(Name="Name") subject.import_presentation_styles("IfcFillAreaStyle") - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() assert props.styles[0].ifc_definition_id == style.id() assert props.styles[0].name == "Name" assert props.styles[0].total_elements == 0 @@ -424,7 +427,7 @@ class TestImportPresentationStyles(NewFile): tool.Ifc.set(ifc) style = ifc.createIfcSurfaceStyle(Name="Name") subject.import_presentation_styles("IfcSurfaceStyle") - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() assert props.styles[0].ifc_definition_id == style.id() assert props.styles[0].name == "Name" assert props.styles[0].total_elements == 0 @@ -434,7 +437,7 @@ class TestImportPresentationStyles(NewFile): tool.Ifc.set(ifc) style = ifc.createIfcTextStyle(Name="Name") subject.import_presentation_styles("IfcTextStyle") - props = bpy.context.scene.BIMStylesProperties + props = tool.Style.get_style_props() assert props.styles[0].ifc_definition_id == style.id() assert props.styles[0].name == "Name" assert props.styles[0].total_elements == 0 @@ -442,9 +445,10 @@ class TestImportPresentationStyles(NewFile): class TestIsEditingStyles(NewFile): def test_run(self): - bpy.context.scene.BIMStylesProperties.is_editing = False + props = tool.Style.get_style_props() + props.is_editing = False assert subject.is_editing_styles() is False - bpy.context.scene.BIMStylesProperties.is_editing = True + props.is_editing = True assert subject.is_editing_styles() is True diff --git a/src/ifcpatch/ifcpatch/__init__.py b/src/ifcpatch/ifcpatch/__init__.py index 8303e90a3a..51f37eacb8 100644 --- a/src/ifcpatch/ifcpatch/__init__.py +++ b/src/ifcpatch/ifcpatch/__init__.py @@ -133,7 +133,7 @@ def write(output: Union[ifcopenshell.file, str], filepath: str) -> None: def extract_docs( - submodule_name: str, cls_name: str, method_name: str = "__init__", boilerplate_args: Optional[Iterable[str]] = None + submodule_name: str, cls_name: str, method_name: str = "__init__", boilerplate_args: Optional[Sequence[str]] = None ) -> Union["PatcherDoc", None]: """Extract class docstrings and method arguments @@ -173,7 +173,7 @@ class InputDoc(TypedDict): filter_glob: NotRequired[str] -def _extract_docs(cls: type, method_name: str, boilerplate_args: Union[Iterable[str], None]) -> PatcherDoc: +def _extract_docs(cls: type, method_name: str, boilerplate_args: Union[Sequence[str], None]) -> PatcherDoc: inputs: dict[str, InputDoc] = {} method = getattr(cls, method_name) if boilerplate_args is None: