diff --git a/src/blenderbim/blenderbim/bim/module/style/__init__.py b/src/blenderbim/blenderbim/bim/module/style/__init__.py index e2ec666495..09fe3102aa 100644 --- a/src/blenderbim/blenderbim/bim/module/style/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/style/__init__.py @@ -52,6 +52,7 @@ classes = ( prop.BIMStyleProperties, ui.BIM_PT_styles, ui.BIM_UL_styles, + ui.BIM_PT_style, ) diff --git a/src/blenderbim/blenderbim/bim/module/style/data.py b/src/blenderbim/blenderbim/bim/module/style/data.py index 9a6cab4819..8e0d7571b9 100644 --- a/src/blenderbim/blenderbim/bim/module/style/data.py +++ b/src/blenderbim/blenderbim/bim/module/style/data.py @@ -20,10 +20,12 @@ import bpy import ifcopenshell import blenderbim.tool as tool from ifcopenshell.util.doc import get_entity_doc +from typing import Union def refresh(): StylesData.is_loaded = False + BlenderMaterialStyleData.is_loaded = False class StylesData: @@ -67,3 +69,55 @@ class StylesData: @classmethod def total_styles(cls): return len(tool.Ifc.get().by_type("IfcPresentationStyle")) + + +class BlenderMaterialStyleData: + style: Union[ifcopenshell.entity_instance, None] = None + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = { + "is_linked_to_style": cls.is_linked_to_style(), + # After is_linked_to_style. + "linked_style_name": cls.linked_style_name(), + } + cls.is_loaded = True + + @classmethod + def is_linked_to_style(cls) -> bool: + ifc_file = tool.Ifc.get() + if not ifc_file: + return False + context = bpy.context + cls.linked_style = None + obj = context.active_object + if not obj: + return False + material = obj.active_material + if not material: + return False + props = material.BIMMaterialProperties + style_id = props.ifc_style_id + style = tool.Ifc.get_entity_by_id(style_id) + if not style: + return False + + # Double check that it's the right material + # and not material from other Blender session. + linked_material = tool.Ifc.get_object(style) + if linked_material != material: + return False + cls.style = style + return True + + @classmethod + def linked_style_name(cls) -> Union[str, None]: + """Return str if Blender Material is linked to IFC style or None if it's not. + + Method will return "Unnamed" if style name is None. + """ + if cls.style: + return cls.style.Name or "Unnamed" + return None diff --git a/src/blenderbim/blenderbim/bim/module/style/operator.py b/src/blenderbim/blenderbim/bim/module/style/operator.py index 79d20bacc4..08eec96e90 100644 --- a/src/blenderbim/blenderbim/bim/module/style/operator.py +++ b/src/blenderbim/blenderbim/bim/module/style/operator.py @@ -96,11 +96,48 @@ class AddStyle(bpy.types.Operator, tool.Ifc.Operator): class UnlinkStyle(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.unlink_style" bl_label = "Unlink Style" + bl_description = ( + "Unlink Blender material from it's linked IFC style.\n\n" + "You can either remove style the material is linked to from IFC or keep it. " + "Note that keeping the unlinked style in IFC might lead to unpredictable issues" + "and should be used only by advanced users" + ) bl_options = {"REGISTER", "UNDO"} - style: bpy.props.IntProperty(default=0) + blender_material: bpy.props.StringProperty(name="Blender Material") + should_delete: bpy.props.BoolProperty(name="Delete IFC Style", default=True) + + def draw(self, context): + row = self.layout.row() + row.prop(self, "should_delete") + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self) def _execute(self, context): - core.unlink_style(tool.Ifc, style=tool.Ifc.get().by_id(self.style)) + # TODO: move to core.style + # Don't check blender_material and style_id as this operator is only called from UI. + assert isinstance(self.blender_material, str) # Type checker. + material = bpy.data.materials[self.blender_material] + style_id = material.BIMMaterialProperties.ifc_style_id + style = tool.Ifc.get_entity_by_id(style_id) + + # Material is linked to a style from a different project. + if not style or tool.Ifc.get_object(style) != material: + tool.Ifc.unlink(obj=material) + return {"FINISHED"} + + if self.should_delete: + # Create a copy that will be removed + # and leave user with unlinked original material. + # It's needed so we don't need to search everywhere original + # material was used and replace it with the unlinked version. + material_copy = material.copy() + tool.Ifc.unlink(element=style) + tool.Ifc.link(style, material_copy) + core.remove_style(tool.Ifc, tool.Style, style) + else: + tool.Ifc.unlink(element=style) + return {"FINISHED"} class EnableEditingStyle(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/style/ui.py b/src/blenderbim/blenderbim/bim/module/style/ui.py index 4fbfee85fb..eb25db8035 100644 --- a/src/blenderbim/blenderbim/bim/module/style/ui.py +++ b/src/blenderbim/blenderbim/bim/module/style/ui.py @@ -21,8 +21,7 @@ import blenderbim.bim.helper import blenderbim.tool as tool from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore -from blenderbim.bim.module.style.data import StylesData -from bl_ui.properties_material import MaterialButtonsPanel +from blenderbim.bim.module.style.data import StylesData, BlenderMaterialStyleData class BIM_PT_styles(Panel): @@ -67,8 +66,6 @@ class BIM_PT_styles(Panel): row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF").style = style.ifc_definition_id op = row.operator("bim.assign_style_to_selected", text="", icon="BRUSH_DATA") op.style_id = style.ifc_definition_id - op = row.operator("bim.unlink_style", text="", icon="UNLINKED") - op.style = style.ifc_definition_id op = row.operator("bim.enable_editing_style", text="", icon="GREASEPENCIL") op.style = style.ifc_definition_id row.operator("bim.remove_style", text="", icon="X").style = style.ifc_definition_id @@ -291,3 +288,43 @@ class BIM_UL_styles(UIList): row2.label(text="", icon="LIGHT_POINT") elif style.name == "IfcExternallyDefinedSurfaceStyle": row2.label(text="", icon="APPEND_BLEND") + + +class BIM_PT_style(Panel): + bl_label = "Style" + bl_idname = "BIM_PT_style" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "material" + + @classmethod + def poll(cls, context): + return bool(tool.Ifc.get() and context.material.BIMMaterialProperties.ifc_style_id) + + def draw(self, context): + # NOTE: this UI is needed only to indicate whether blender material is linked to IFC + # and shouldn't be overloaded with any other features (they should be added to the general Styles UI). + + if not BlenderMaterialStyleData.is_loaded: + BlenderMaterialStyleData.load() + + material = context.material + style_id = material.BIMMaterialProperties.ifc_style_id + row = self.layout.row(align=True) + + if style_id and not BlenderMaterialStyleData.data["is_linked_to_style"]: + row.label(text="Material has linked IFC from a different project.") + op = row.operator("bim.unlink_style", icon="UNLINKED", text="") + op.blender_material = material.name + return + else: + row.label(text="Material is linked to an IFC style.") + op = row.operator("bim.unlink_style", icon="UNLINKED", text="") + op.blender_material = material.name + + row = self.layout.row(align=True) + row.label(text="IFC Style ID:") + row.label(text=str(style_id)) + row = self.layout.row(align=True) + row.label(text="IFC Style Name:") + row.label(text=BlenderMaterialStyleData.data["linked_style_name"])