From fa31e10a7732b902da003dfa034b6509abd18e71 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 7 Dec 2023 15:25:20 +0500 Subject: [PATCH] Styles UI - moved UI from shader graph N-panel to Styles UI #3912 Moved all shading style and textures parameters from shader-graph N-panel to general Styles UI. Shader graph now also update in real time on any prop changes in Styles UI. Now there is practically no need to switch to shader graph to edit styles shaders - all parameters are available from the Styles UI. That also allows you to edit styles and see the results in viewport even if the edited style is not the one that's currently active. small demo - https://imgur.com/a/i2suoZw --- .../blenderbim/bim/module/style/__init__.py | 5 +- .../blenderbim/bim/module/style/data.py | 11 + .../blenderbim/bim/module/style/operator.py | 256 +++++++++--------- .../blenderbim/bim/module/style/prop.py | 235 +++++++--------- .../blenderbim/bim/module/style/ui.py | 134 ++++----- src/blenderbim/blenderbim/tool/loader.py | 70 +++-- src/blenderbim/blenderbim/tool/style.py | 152 ++++++----- src/blenderbim/test/bim/feature/style.feature | 2 +- src/blenderbim/test/tool/test_loader.py | 13 +- 9 files changed, 425 insertions(+), 453 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/style/__init__.py b/src/blenderbim/blenderbim/bim/module/style/__init__.py index c63d33ed0c..6b404ccf70 100644 --- a/src/blenderbim/blenderbim/bim/module/style/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/style/__init__.py @@ -23,8 +23,9 @@ classes = ( operator.ActivateExternalStyle, operator.AddPresentationStyle, operator.AddStyle, + operator.AddSurfaceTexture, operator.BrowseExternalStyle, - operator.ClearTextureMapPath, + operator.RemoveTextureMap, operator.ChooseTextureMapPath, operator.DisableAddingPresentationStyle, operator.DisableEditingExternalStyle, @@ -46,6 +47,7 @@ classes = ( operator.UpdateStyleColours, operator.UpdateStyleTextures, prop.Style, + prop.Texture, prop.BIMStylesProperties, prop.BIMStyleProperties, ui.BIM_PT_styles, @@ -53,7 +55,6 @@ classes = ( ui.BIM_PT_style_attributes, ui.BIM_PT_external_style_attributes, ui.BIM_UL_styles, - ui.BIM_PT_STYLE_GRAPH, ) diff --git a/src/blenderbim/blenderbim/bim/module/style/data.py b/src/blenderbim/blenderbim/bim/module/style/data.py index d11b766246..50447bec85 100644 --- a/src/blenderbim/blenderbim/bim/module/style/data.py +++ b/src/blenderbim/blenderbim/bim/module/style/data.py @@ -34,12 +34,23 @@ class StylesData: @classmethod def load(cls): cls.data = { + "styles_to_blender_material_names": cls.styles_to_blender_material_names(), "style_types": cls.style_types(), "total_styles": cls.total_styles(), "reflectance_methods": cls.reflectance_methods(), } cls.is_loaded = True + @classmethod + def styles_to_blender_material_names(cls): + ifc_file = tool.Ifc.get() + props = bpy.context.scene.BIMStylesProperties + materials = [] + for style in props.styles: + material = tool.Ifc.get_object(ifc_file.by_id(style.ifc_definition_id)) + materials.append(material.name if material is not None else None) + return materials + @classmethod def reflectance_methods(cls): declaration = tool.Ifc.schema().declaration_by_name("IfcReflectanceMethodEnum") diff --git a/src/blenderbim/blenderbim/bim/module/style/operator.py b/src/blenderbim/blenderbim/bim/module/style/operator.py index 718c88b0c2..9b3acf720c 100644 --- a/src/blenderbim/blenderbim/bim/module/style/operator.py +++ b/src/blenderbim/blenderbim/bim/module/style/operator.py @@ -24,8 +24,7 @@ import blenderbim.tool as tool import blenderbim.core.style as core import ifcopenshell.util.representation from pathlib import Path -from blenderbim.bim.ifc import IfcStore -from blenderbim.bim.module.style.data import StylesData, StyleAttributesData +from blenderbim.bim.module.style.data import StyleAttributesData from mathutils import Vector @@ -33,14 +32,15 @@ class UpdateStyleColours(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.update_style_colours" bl_label = "Save Current Shading Style" bl_description = ( - "Save current style values to IfcSurfaceStyleShading.\n\n" + "ALT+CLICK to see saved values details" + "Update IfcSurfaceStyleShading based on current blender material shading graph.\n\n" + + "ALT+CLICK to see saved values details" ) bl_options = {"REGISTER", "UNDO"} verbose: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) def invoke(self, context, event): - # verobse print to console on alt+click + # verbose print to console on alt+click # make sure to use SKIP_SAVE on property, otherwise it might get stuck if event.type == "LEFTMOUSE" and event.alt: self.verbose = True @@ -51,12 +51,12 @@ class UpdateStyleColours(bpy.types.Operator, tool.Ifc.Operator): core.update_style_colours(tool.Ifc, tool.Style, obj=mat, verbose=self.verbose) if self.verbose: self.report({"INFO"}, "Check the system console to see saved style properties") - tool.Style.set_surface_style_props(mat) class UpdateStyleTextures(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.update_style_textures" bl_label = "Update Style Textures" + bl_description = "Update IfcSurfaceStyleWithTextures based on current blender material shading graph" bl_options = {"REGISTER", "UNDO"} def _execute(self, context): @@ -82,6 +82,7 @@ class RemoveStyle(bpy.types.Operator, tool.Ifc.Operator): class AddStyle(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_style" bl_label = "Add Style" + bl_description = "Add IfcSurfaceStyle to the active material" bl_options = {"REGISTER", "UNDO"} def _execute(self, context): @@ -120,6 +121,12 @@ class DisableEditingStyle(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): props = bpy.context.scene.BIMStylesProperties + + style = tool.Ifc.get().by_id(props.is_editing_style) + material = tool.Ifc.get_object(style) + # just to trigger style update + material.BIMStyleProperties.active_style_type = material.BIMStyleProperties.active_style_type + props.is_editing_style = 0 @@ -146,19 +153,14 @@ class UpdateCurrentStyle(bpy.types.Operator): ) bl_options = {"REGISTER", "UNDO"} update_all: bpy.props.BoolProperty(name="Update All", default=False, options={"SKIP_SAVE"}) + style_id: bpy.props.IntProperty(default=0, options={"SKIP_SAVE"}) @classmethod def poll(cls, context): - poll = ( - context.active_object is not None - and context.active_object.active_material is not None - and context.active_object.active_material.BIMMaterialProperties.ifc_style_id != 0 - ) - if not poll: - cls.poll_message_set( - "Object is not selected or material is not assigned or material doesn't have IFC Style" - ) - return poll + if not context.selected_objects: + cls.poll_message_set("No objects selected") + return False + return True def invoke(self, context, event): # updating all styles on shift+click @@ -168,16 +170,18 @@ class UpdateCurrentStyle(bpy.types.Operator): return self.execute(context) def execute(self, context): - current_style_type = context.active_object.active_material.BIMStyleProperties.active_style_type + style = tool.Ifc.get().by_id(self.style_id) + material = tool.Ifc.get_object(style) + current_style_type = material.BIMStyleProperties.active_style_type + if self.update_all: context.scene.BIMStylesProperties.active_style_type = current_style_type return {"FINISHED"} - materials = [] for obj in context.selected_objects: - mat = obj.active_material - if mat and mat.BIMMaterialProperties.ifc_style_id != 0: - mat.BIMStyleProperties.active_style_type = current_style_type + for mat in obj.data.materials: + if mat and mat.BIMMaterialProperties.ifc_style_id != 0: + mat.BIMStyleProperties.active_style_type = current_style_type return {"FINISHED"} @@ -231,7 +235,9 @@ class BrowseExternalStyle(bpy.types.Operator): description="List of objects in the .blend file", items=get_data_blocks, ) - use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True) + use_relative_path: bpy.props.BoolProperty( + name="Use Relative Path", description="Save path relative to IFC file", default=True + ) directory: bpy.props.StringProperty( name="Directory", description="Start file browsing directory", @@ -266,11 +272,11 @@ class BrowseExternalStyle(bpy.types.Operator): def execute(self, context): if self.data_block_type == "0": - self.report({"ERROR"}, "Select a data block type") + self.report({"ERROR"}, "Select a data block type in the side panel of the file browser") return {"CANCELLED"} if self.data_block == "": - self.report({"ERROR"}, "Select a data block") + self.report({"ERROR"}, "Select a data block in the side panel of the file browser") return {"CANCELLED"} if not os.path.exists(self.filepath): @@ -413,10 +419,12 @@ class SelectByStyle(bpy.types.Operator, tool.Ifc.Operator): class ChooseTextureMapPath(bpy.types.Operator): bl_idname = "bim.choose_texture_map_path" bl_label = "Choose Texture Map Path" - bl_options = {"REGISTER", "UNDO"} - texture_map_prop: bpy.props.StringProperty(default="") + bl_options = {"REGISTER", "UNDO", "INTERNAL"} + texture_map_index: bpy.props.IntProperty(default=-1, options={"SKIP_SAVE"}) - use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True) + use_relative_path: bpy.props.BoolProperty( + name="Use Relative Path", description="Save path relative to IFC file", default=True + ) filepath: bpy.props.StringProperty( name="File Path", description="Filepath used to import from", maxlen=1024, default="", subtype="FILE_PATH" ) @@ -427,16 +435,9 @@ class ChooseTextureMapPath(bpy.types.Operator): context.window_manager.fileselect_add(self) return {"RUNNING_MODAL"} - @classmethod - def poll(cls, context): - poll = getattr(context, "material", None) - if not poll: - cls.poll_message_set("Select a material") - return poll - def execute(self, context): - if not self.texture_map_prop: - self.report({"ERROR"}, "Provide a texture map") + if self.texture_map_index < 0: + self.report({"ERROR"}, "Provide a texture map index") return {"CANCELLED"} abs_path = Path(self.filepath) @@ -445,30 +446,26 @@ class ChooseTextureMapPath(bpy.types.Operator): else: image_filepath = abs_path - props = context.material.BIMStyleProperties - setattr(props, self.texture_map_prop, image_filepath.as_posix()) + texture = context.scene.BIMStylesProperties.textures[self.texture_map_index] + texture.path = image_filepath.as_posix() return {"FINISHED"} -class ClearTextureMapPath(bpy.types.Operator): - bl_idname = "bim.clear_texture_map_path" - bl_label = "Clear Texture Map Path" - bl_options = {"REGISTER", "UNDO"} - texture_map_prop: bpy.props.StringProperty(default="") - - @classmethod - def poll(cls, context): - poll = getattr(context, "material", None) - if not poll: - cls.poll_message_set("Select a material") - return poll +class RemoveTextureMap(bpy.types.Operator): + bl_idname = "bim.remove_texture_map" + bl_label = "Remove Texture Map" + bl_options = {"REGISTER", "UNDO", "INTERNAL"} + texture_map_index: bpy.props.IntProperty(default=-1, options={"SKIP_SAVE"}) def execute(self, context): - if not self.texture_map_prop: - self.report({"ERROR"}, "Provide a texture map") + if self.texture_map_index < 0: + self.report({"ERROR"}, "Provide a texture map index") return {"CANCELLED"} - props = context.material.BIMStyleProperties - setattr(props, self.texture_map_prop, "") + + props = context.scene.BIMStylesProperties + props.textures.remove(self.texture_map_index) + # just to trigger shader graph update + props.surface_colour = props.surface_colour return {"FINISHED"} @@ -546,74 +543,15 @@ class EnableEditingSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): props = bpy.context.scene.BIMStylesProperties style = tool.Ifc.get().by_id(self.style) - - shading = None - - surface_style = None - for style2 in style.Styles: - if style2.is_a() == self.ifc_class: - surface_style = style2 - if style2.is_a() == "IfcSurfaceStyleShading": - shading = style2 - - color_to_tuple = lambda x: (x.Red, x.Green, x.Blue) - - if surface_style: - if self.ifc_class == "IfcSurfaceStyleShading": - props.surface_colour = color_to_tuple(surface_style.SurfaceColour) - props.transparency = surface_style.Transparency or 0.0 - elif self.ifc_class == "IfcSurfaceStyleRendering": - props.surface_colour = color_to_tuple(surface_style.SurfaceColour) - props.transparency = surface_style.Transparency or 0.0 - - if surface_style.DiffuseColour: - props.is_diffuse_colour_null = False - if surface_style.DiffuseColour.is_a("IfcColourRgb"): - props.diffuse_colour_class = "IfcColourRgb" - props.diffuse_colour = color_to_tuple(surface_style.DiffuseColour) - else: - props.diffuse_colour_class = "IfcNormalisedRatioMeasure" - props.diffuse_colour_ratio = surface_style.DiffuseColour.wrappedValue - else: - props.is_diffuse_colour_null = False - - if surface_style.SpecularColour: - props.is_specular_colour_null = False - if surface_style.SpecularColour.is_a("IfcColourRgb"): - props.specular_colour_class = "IfcColourRgb" - props.specular_colour = color_to_tuple(surface_style.SpecularColour) - else: - props.specular_colour_class = "IfcNormalisedRatioMeasure" - props.specular_colour_ratio = surface_style.SpecularColour.wrappedValue - else: - props.is_specular_colour_null = False - - if surface_style.SpecularHighlight: - props.is_specular_highlight_null = False - if surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"): - props.specular_highlight = surface_style.SpecularHighlight.wrappedValue - else: - props.is_specular_highlight_null = False # Exponent is meaningless - else: - props.is_specular_highlight_null = True - - props.reflectance_method = surface_style.ReflectanceMethod - elif self.ifc_class == "IfcExternallyDefinedSurfaceStyle": - attributes = props.external_style_attributes - attributes.clear() - blenderbim.bim.helper.import_attributes2(surface_style, attributes) - else: - if self.ifc_class == "IfcSurfaceStyleRendering": - if shading: - props.surface_colour = color_to_tuple(shading.SurfaceColour) - props.transparency = shading.Transparency or 0.0 - elif self.ifc_class == "IfcExternallyDefinedSurfaceStyle": - attributes = props.external_style_attributes - attributes.clear() - blenderbim.bim.helper.import_attributes2(self.ifc_class, attributes) - props.is_editing_style = self.style props.is_editing_class = self.ifc_class + tool.Style.set_surface_style_props() + + surface_style = tool.Style.get_style_elements(style).get(self.ifc_class, None) + if self.ifc_class == "IfcExternallyDefinedSurfaceStyle": + attributes = props.external_style_attributes + attributes.clear() + blenderbim.bim.helper.import_attributes2(surface_style or self.ifc_class, attributes) class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator): @@ -625,16 +563,11 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator): self.props = bpy.context.scene.BIMStylesProperties self.style = tool.Ifc.get().by_id(self.props.is_editing_style) - self.surface_style = None - self.shading_style = None - self.rendering_style = None - for style2 in self.style.Styles: - if style2.is_a() == self.props.is_editing_class: - self.surface_style = style2 - if style2.is_a() == "IfcSurfaceStyleShading": - self.shading_style = style2 - if style2.is_a() == "IfcSurfaceStyleRendering": - self.rendering_style = style2 + style_elements = tool.Style.get_style_elements(self.style) + self.surface_style = style_elements.get(self.props.is_editing_class, None) + self.shading_style = style_elements.get("IfcSurfaceStyleShading", None) + self.rendering_style = style_elements.get("IfcSurfaceStyleRendering", None) + self.texture_style = style_elements.get("IfcSurfaceStyleWithTextures", None) if self.surface_style: self.edit_existing_style() @@ -665,8 +598,27 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator): attributes=self.get_rendering_attributes(), ) tool.Loader.create_surface_style_rendering(material, self.surface_style) + elif self.props.is_editing_class == "IfcSurfaceStyleWithTextures": + # TODO: fix same issues as with creating new IfcSurfaceStyleWithTextures + material = tool.Ifc.get_object(self.style) + shading_style = self.rendering_style or self.shading_style + if self.rendering_style is None: + self.report( + {"ERROR"}, + "Editing texture styles without defining shading/rendering style is not yet supported. " + "Define shading/render style first", + ) + return {"CANCELLED"} + textures = tool.Ifc.run("style.add_surface_textures", material=material, uv_maps=[]) + texture_style = tool.Ifc.run( + "style.add_surface_style", + style=self.style, + ifc_class="IfcSurfaceStyleWithTextures", + attributes={"Textures": textures}, + ) + tool.Loader.create_surface_style_with_textures(material, shading_style, texture_style) elif self.surface_style.is_a() == "IfcExternallyDefinedSurfaceStyle": - surface_style = ifcopenshell.api.run( + ifcopenshell.api.run( "style.edit_surface_style", tool.Ifc.get(), style=self.surface_style, @@ -693,6 +645,30 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator): attributes=self.get_rendering_attributes(), ) tool.Loader.create_surface_style_rendering(material, surface_style) + elif self.props.is_editing_class == "IfcSurfaceStyleWithTextures": + # TODO: rework add_surface_textures to work without blender + # otherwise we lose textures that are not used in the shader + # and we also doesn't recognize relative paths if .blend file is not saved + # TODO: provide `uv_maps` - need to rework .get_uv_maps not to depend on a single representation + material = tool.Ifc.get_object(self.style) + shading_style = self.rendering_style or self.shading_style + # TODO: support creating texture styles without defining shading style first + if self.rendering_style is None: + self.report( + {"ERROR"}, + "Creating texture styles without defining shading/rendering style is not yet supported. " + "Define shading/render style first", + ) + return {"CANCELLED"} + textures = tool.Ifc.run("style.add_surface_textures", material=material, uv_maps=[]) + if textures: + texture_style = tool.Ifc.run( + "style.add_surface_style", + style=self.style, + ifc_class="IfcSurfaceStyleWithTextures", + attributes={"Textures": textures}, + ) + tool.Loader.create_surface_style_with_textures(material, shading_style, texture_style) elif self.props.is_editing_class == "IfcExternallyDefinedSurfaceStyle": surface_style = ifcopenshell.api.run( "style.add_surface_style", @@ -726,7 +702,7 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator): if self.props.is_specular_highlight_null: specular_highlight = None else: - specular_highlight = {"SpecularRoughness": self.props.specular_highlight} + specular_highlight = {"IfcSpecularRoughness": self.props.specular_highlight} return { "SurfaceColour": self.color_to_dict(self.props.surface_colour), @@ -741,6 +717,24 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator): return {"Red": x[0], "Green": x[1], "Blue": x[2]} +class AddSurfaceTexture(bpy.types.Operator): + bl_idname = "bim.add_surface_texture" + bl_label = "Add Surface Texture" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + if len(context.scene.BIMStylesProperties.textures) >= 8: + cls.poll_message_set("Only 8 texture maps available") + return False + return True + + def execute(self, context): + props = context.scene.BIMStylesProperties + props.textures.add() + return {"FINISHED"} + + class SaveUVToStyle(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.save_uv_to_style" bl_label = "Save UV To Style" diff --git a/src/blenderbim/blenderbim/bim/module/style/prop.py b/src/blenderbim/blenderbim/bim/module/style/prop.py index 5995189aba..81c922a5ef 100644 --- a/src/blenderbim/blenderbim/bim/module/style/prop.py +++ b/src/blenderbim/blenderbim/bim/module/style/prop.py @@ -17,7 +17,7 @@ # along with BlenderBIM Add-on. If not, see . import bpy -from blenderbim.bim.ifc import IfcStore +import blenderbim.tool as tool from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.module.style.data import StylesData from bpy.types import PropertyGroup @@ -31,7 +31,6 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) -import blenderbim.tool as tool def get_style_types(self, context): @@ -74,6 +73,45 @@ def update_shading_styles(self, context): tool.Style.change_current_style_type(mat, self.active_style_type) +def update_shader_graph(self, context): + props = self.id_data.BIMStylesProperties if isinstance(self, Texture) else self + + if not props.update_graph: + return + style = tool.Ifc.get().by_id(props.is_editing_style) + material = tool.Ifc.get_object(style) + + shading_data = tool.Style.get_shading_style_data_from_props() + textures_data = tool.Style.get_texture_style_data_from_props() + tool.Loader.create_surface_style_rendering(material, shading_data) + tool.Loader.create_surface_style_with_textures(material, shading_data, textures_data) + + +UV_MODES = [ + ("UV", "UV", "Actual UV data presented on the geometry"), + ("Generated", "Generated", "Automatically-generated UV from the vertex positions of the mesh"), + ("Camera", "Camera", "UV from position coordinate in camera space"), +] + + +TEXTURE_MAPS_MODS = ( + ("DIFFUSE", "DIFFUSE", ""), + ("NORMAL", "NORMAL", ""), + ("METALLICROUGHNESS", "METALLICROUGHNESS", "Green Channel = Roughness,\nBlue Channel = Metallic"), + ("SPECULAR", "SPECULAR", ""), + ("SHININESS", "SHININESS", ""), + ("EMISSIVE", "EMISSIVE", ""), + ("OCCLUSION", "OCCLUSION", ""), + ("AMBIENT", "AMBIENT", ""), +) + + +class Texture(PropertyGroup): + mode: EnumProperty(name="Type Of Texture", items=TEXTURE_MAPS_MODS, update=update_shader_graph) + # NOTE: subtype `FILE_PATH` is not used to avoid .blend relative paths + path: StringProperty(name="Texture Path", update=update_shader_graph) + + class BIMStylesProperties(PropertyGroup): is_adding: BoolProperty(name="Is Adding") is_editing: BoolProperty(name="Is Editing") @@ -98,31 +136,81 @@ class BIMStylesProperties(PropertyGroup): name="Surface Style Class", default="IfcSurfaceStyleShading", ) - surface_colour: bpy.props.FloatVectorProperty( - name="Surface Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3 + update_graph: BoolProperty( + name="Update Shade Graph on Prop Change", + description="Update shader graph in real time\nas you update style properties", + default=True, ) - transparency: bpy.props.FloatProperty(name="Transparency", default=0.0, min=0.0, max=1.0) + + # shading props + surface_colour: bpy.props.FloatVectorProperty( + name="Surface Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3, update=update_shader_graph + ) + transparency: bpy.props.FloatProperty( + name="Transparency", default=0.0, min=0.0, max=1.0, update=update_shader_graph + ) + # 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")], name="Diffuse Colour Class", + update=update_shader_graph, ) diffuse_colour: bpy.props.FloatVectorProperty( - name="Diffuse Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3 + name="Diffuse Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3, update=update_shader_graph + ) + diffuse_colour_ratio: bpy.props.FloatProperty( + name="Diffuse Ratio", default=0.0, min=0.0, max=1.0, update=update_shader_graph ) - diffuse_colour_ratio: bpy.props.FloatProperty(name="Diffuse Ratio", default=0.0, min=0.0, max=1.0) is_specular_colour_null: BoolProperty(name="Is Null") specular_colour_class: EnumProperty( items=[(x, x, "") for x in ("IfcColourRgb", "IfcNormalisedRatioMeasure")], name="Specular Colour Class", + update=update_shader_graph, + default="IfcNormalisedRatioMeasure", ) specular_colour: bpy.props.FloatVectorProperty( - name="Specular Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3 + name="Specular Colour", + subtype="COLOR", + default=(1, 1, 1), + min=0.0, + max=1.0, + size=3, + update=update_shader_graph, + ) + specular_colour_ratio: bpy.props.FloatProperty( + name="Specular Ratio", + description="Used as Metallic value in PHYSICAL Reflectance Method", + default=0.0, + min=0.0, + max=1.0, + update=update_shader_graph, ) - specular_colour_ratio: bpy.props.FloatProperty(name="Specular Ratio", default=0.0, min=0.0, max=1.0) is_specular_highlight_null: BoolProperty(name="Is Null") - specular_highlight: bpy.props.FloatProperty(name="Specular Highlight", default=0.0, min=0.0, max=1.0) - reflectance_method: EnumProperty(name="Reflectance Method", items=get_reflectance_methods) + specular_highlight: bpy.props.FloatProperty( + name="Specular Highlight", + description="Used as Roughness value in PHYSICAL Reflectance Method", + default=0.0, + min=0.0, + max=1.0, + update=update_shader_graph, + ) + reflectance_method: EnumProperty( + name="Reflectance Method", + items=get_reflectance_methods, + update=update_shader_graph, + ) + + # textures props + textures: CollectionProperty(name="Textures", type=Texture) + uv_mode: EnumProperty( + name="UV Mode", + description="Type of UV used for the textures", + items=UV_MODES, + default="UV", + update=update_shader_graph, + ) + styles: CollectionProperty(name="Styles", type=Style) active_style_index: IntProperty(name="Active Style Index") active_style_type: EnumProperty( @@ -157,54 +245,13 @@ def update_shading_style(self, context): if rendering_style and texture_style: tool.Loader.create_surface_style_with_textures(blender_material, rendering_style, texture_style) - tool.Style.set_surface_style_props(blender_material) tool.Style.record_shading(blender_material) -# TODO: support more more methods -# based on https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcReflectanceMethodEnum.htm -REFLECTANCE_METHODS = [ - ("PHYSICAL", "PHYSICAL", ""), - ("FLAT", "FLAT", ""), - ("METAL", "METAL", ""), - ("MATT", "MATT", ""), - ("GLASS", "GLASS", ""), - ("NOTDEFINED", "NOTDEFINED", ""), -] - -UV_MODES = [ - ("UV", "UV", "Actual UV data presented on the geometry"), - ("Generated", "Generated", "Automatically-generated UV from the vertex positions of the mesh"), - ("Camera", "Camera", "UV from position coordinate in camera space"), -] - - -def update_shader_graph(self, context): - if not self.update_graph: - return - - material = self.id_data - style_data = tool.Style.get_surface_style_from_props(material) - textures_data = tool.Style.get_texture_style_from_props(material) - tool.Loader.create_surface_style_rendering(material, style_data) - tool.Loader.create_surface_style_with_textures(material, style_data, textures_data) - - -def update_graph_get(self): - return self.get("update_graph", True) - - -def update_graph_set(self, value): - self["update_graph"] = value - if value: - material = self.id_data - tool.Style.set_surface_style_props(material) - - class BIMStyleProperties(PropertyGroup): + # TODO: remove, as attributes already moved to styles ui attributes: CollectionProperty(name="Attributes", type=Attribute) is_editing: BoolProperty(name="Is Editing") - external_style_attributes: CollectionProperty(name="External Style Attributes", type=Attribute) is_editing_external_style: BoolProperty(name="Is Editing External Style") @@ -215,85 +262,3 @@ class BIMStyleProperties(PropertyGroup): default="Shading", update=update_shading_style, ) - - update_graph: BoolProperty( - name="Update Shade Graph on Prop Change", - description="Update shader graph in real time\nas you update style properties", - default=True, - get=update_graph_get, - set=update_graph_set, - ) - - uv_mode: EnumProperty( - name="UV Mode", - description="Type of UV used for the textures", - items=UV_MODES, - default="UV", - update=update_shader_graph, - ) - - # GLTF style properties - reflectance_method: EnumProperty( - name="Reflectance Method", - description="Reflectance method to use for the material", - items=REFLECTANCE_METHODS, - default="PHYSICAL", - update=update_shader_graph, - ) - surface_color: bpy.props.FloatVectorProperty( - name="Surface Color", - subtype="COLOR", - default=(1, 1, 1, 1), - min=0.0, - max=1.0, - size=4, - update=update_shader_graph, - ) - diffuse_color: bpy.props.FloatVectorProperty( - name="Diffuse Color", - subtype="COLOR", - default=(1, 1, 1, 1), - min=0.0, - max=1.0, - size=4, - update=update_shader_graph, - ) - transparency: bpy.props.FloatProperty( - name="Transparency", default=0.0, min=0.0, max=1.0, update=update_shader_graph - ) - roughness: bpy.props.FloatProperty(name="Roughness", default=0.0, min=0.0, max=1.0, update=update_shader_graph) - metallic: bpy.props.FloatProperty(name="Metallic", default=0.0, min=0.0, max=1.0, update=update_shader_graph) - - # texture paths - normal_path: bpy.props.StringProperty( - name="NormalMap", - maxlen=1024, - default="", - update=update_shader_graph, - ) - emissive_path: bpy.props.StringProperty( - name="Emissive", - maxlen=1024, - default="", - update=update_shader_graph, - ) - metallic_roughness_path: bpy.props.StringProperty( - name="Metallic/Roughness", - maxlen=1024, - default="", - update=update_shader_graph, - description="Green Channel = Roughness,\nBlue Channel = Metallic", - ) - diffuse_path: bpy.props.StringProperty( - name="Diffuse", - maxlen=1024, - default="", - update=update_shader_graph, - ) - occlusion_path: bpy.props.StringProperty( - name="Occlusion", - description="Note that occlusion isn't actually used in Blender shader, we're just storing the data", - maxlen=1024, - default="", - update=update_shader_graph, - ) diff --git a/src/blenderbim/blenderbim/bim/module/style/ui.py b/src/blenderbim/blenderbim/bim/module/style/ui.py index b18b9ec20a..d0ea70944b 100644 --- a/src/blenderbim/blenderbim/bim/module/style/ui.py +++ b/src/blenderbim/blenderbim/bim/module/style/ui.py @@ -18,11 +18,11 @@ import bpy 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, StyleAttributesData from bl_ui.properties_material import MaterialButtonsPanel -from blenderbim.tool.style import TEXTURE_MAPS_BY_METHODS, STYLE_TEXTURE_PROPS_MAP class BIM_PT_styles(Panel): @@ -59,6 +59,7 @@ class BIM_PT_styles(Panel): self.layout.template_list("BIM_UL_styles", "", self.props, "styles", self.props, "active_style_index") + # adding a new IfcSurfaceStyle if self.props.is_adding: box = self.layout.box() row = box.row() @@ -75,14 +76,23 @@ class BIM_PT_styles(Panel): row.operator("bim.add_presentation_style", text="Save New Style", icon="CHECKMARK") row.operator("bim.disable_adding_presentation_style", text="", icon="CANCEL") + # style ui tools if self.props.styles and self.props.active_style_index < len(self.props.styles): row = self.layout.row(align=True) style = self.props.styles[self.props.active_style_index] + material_name = StylesData.data["styles_to_blender_material_names"][self.props.active_style_index] + material = bpy.data.materials[material_name] + op = row.operator("bim.enable_editing_style", text="Edit Style", icon="GREASEPENCIL") op.style = style.ifc_definition_id row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF").style = style.ifc_definition_id row.operator("bim.remove_style", text="", icon="X").style = style.ifc_definition_id + row = self.layout.row(align=True) + row.prop(material.BIMStyleProperties, "active_style_type", icon="SHADING_RENDERED", text="") + op = row.operator("bim.update_current_style", icon="FILE_REFRESH", text="") + op.style_id = style.ifc_definition_id + if self.props.style_type == "IfcSurfaceStyle": self.layout.label(text="Surface Style Element:") col = self.layout.column(align=True) @@ -109,6 +119,7 @@ class BIM_PT_styles(Panel): op.ifc_class = "IfcExternallyDefinedSurfaceStyle" op.style = style.ifc_definition_id + # display style elements props during edit if self.props.is_editing_style: if self.props.is_editing_class == "IfcSurfaceStyle": blenderbim.bim.helper.draw_attributes(self.props.attributes, self.layout) @@ -121,8 +132,10 @@ class BIM_PT_styles(Panel): self.draw_surface_style_rendering() elif self.props.is_editing_class == "IfcExternallyDefinedSurfaceStyle": self.draw_externally_defined_surface_style() + elif self.props.is_editing_class == "IfcSurfaceStyleWithTextures": + self.draw_surface_style_with_textures() else: - # TODO: UI for Texture, Lighting, Refract + # TODO: UI Lighting, Refract self.layout.label(text=f"{self.props.is_editing_class} UI is not yet supported.") def draw_surface_style_shading(self): @@ -142,8 +155,12 @@ class BIM_PT_styles(Panel): row = self.layout.row() row.prop(self.props, "reflectance_method") + if self.props.reflectance_method not in ("PHYSICAL", "NOTDEFINED", "FLAT"): + self.layout.label(text=f"Supported reflectance methods are:") + self.layout.label(text=f"PHYSICAL / NOTDEFINED / FLAT") + row = self.layout.row(align=True) - row.label(text="Diffuse") + row.label(text="Emissive" if self.props.reflectance_method == "FLAT" else "Diffuse") row.prop(self.props, "diffuse_colour_class", text="") if self.props.diffuse_colour_class == "IfcColourRgb": row.prop(self.props, "diffuse_colour", text="") @@ -192,6 +209,28 @@ class BIM_PT_styles(Panel): row.operator("bim.edit_surface_style", text="Save Rendering Style", icon="CHECKMARK") row.operator("bim.disable_editing_style", text="", icon="CANCEL") + def draw_surface_style_with_textures(self): + textures = self.props.textures + row = self.layout.row(align=True) + row.label(text=f"Style has {len(textures)} textures", icon="SHADING_TEXTURE") + row.operator("bim.add_surface_texture", text="", icon="ADD") + self.layout.prop(self.props, "uv_mode") + + for i, texture in enumerate(textures): + split = self.layout.split(factor=0.30, align=True) + split.column(align=True).prop(texture, "mode", text="") + + # path + row = split.column(align=True).row(align=True) + row.prop(texture, "path", text="") + op_path = row.operator("bim.choose_texture_map_path", text="", icon="FILEBROWSER") + op_clear = row.operator("bim.remove_texture_map", text="", icon="X") + op_path.texture_map_index = op_clear.texture_map_index = i + + row = self.layout.row(align=True) + row.operator("bim.edit_surface_style", text="Save Texture Style", icon="CHECKMARK") + row.operator("bim.disable_editing_style", text="", icon="CANCEL") + def draw_externally_defined_surface_style(self): blenderbim.bim.helper.draw_attributes(self.props.external_style_attributes, self.layout) row = self.layout.row(align=True) @@ -199,24 +238,6 @@ class BIM_PT_styles(Panel): row.operator("bim.disable_editing_style", text="", icon="CANCEL") -def draw_style_ui(self, context): - mat = context.material - props = mat.BIMMaterialProperties - style_props = mat.BIMStyleProperties - row = self.layout.row(align=True) - if not props.ifc_style_id: - row.operator("bim.add_style", icon="ADD") - return - - row.prop(style_props, "active_style_type", icon="SHADING_RENDERED", text="") - row.operator("bim.update_current_style", icon="FILE_REFRESH", text="") - row = self.layout.row(align=True) - row.operator("bim.update_style_colours", icon="GREASEPENCIL") - row.operator("bim.update_style_textures", icon="TEXTURE", text="") - row.operator("bim.unlink_style", icon="UNLINKED", text="") - row.operator("bim.remove_style", icon="X", text="").style = props.ifc_style_id - - class BIM_PT_style(MaterialButtonsPanel, Panel): bl_label = "Style" bl_idname = "BIM_PT_style" @@ -235,11 +256,15 @@ class BIM_PT_style(MaterialButtonsPanel, Panel): def draw(self, context): mat = context.material props = mat.BIMMaterialProperties - draw_style_ui(self, context) + row = self.layout.row(align=True) if not props.ifc_style_id: + row.operator("bim.add_style", icon="ADD") return row = self.layout.row(align=True) - row.prop(mat, "diffuse_color", text="Viewport Color" if mat.use_nodes else "Render Color") + row.operator("bim.update_style_colours", icon="GREASEPENCIL") + row.operator("bim.update_style_textures", icon="TEXTURE", text="") + row.operator("bim.unlink_style", icon="UNLINKED", text="") + row.operator("bim.remove_style", icon="X", text="").style = props.ifc_style_id class BIM_PT_style_attributes(Panel): @@ -369,66 +394,3 @@ 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_GRAPH(Panel): - bl_idname = "BIM_PT_style_graph" - bl_space_type = "NODE_EDITOR" - bl_label = "Style Graph Settings" - bl_region_type = "UI" - bl_category = "BBIM" - - @classmethod - def poll(cls, context): - return getattr(context, "material", None) - - def draw(self, context): - layout = self.layout - props = context.active_object.active_material.BIMStyleProperties - - draw_style_ui(self, context) - layout.separator() - box = layout.box() - box.label(text="Creating shader from this panel") - box.label(text="ensures that shader is") - box.label(text="GLTF compatible") - box.label(text="and therefore will be ") - box.label(text="stored in IFC safely.") - layout.separator() - - layout.prop(props, "update_graph", text="Graph Auto Update") - layout.label(text="Reflectance Method:") - layout.prop(props, "reflectance_method", text="") - layout.prop(props, "surface_color") - layout.prop(props, "transparency") - - if not (props.reflectance_method == "PHYSICAL" and props.diffuse_path) and not ( - props.reflectance_method == "FLAT" and props.emissive_path - ): - prop_name = "Emissive Color" if props.reflectance_method == "FLAT" else "Diffuse Color" - layout.prop(props, "diffuse_color", text=prop_name) - - if props.reflectance_method == "PHYSICAL" and not props.metallic_roughness_path: - layout.prop(props, "metallic") - - if props.reflectance_method not in ("PHYSICAL", "FLAT", "NOTDEFINED") or ( - props.reflectance_method == "PHYSICAL" and not props.metallic_roughness_path - ): - layout.prop(props, "roughness") - - def add_texture_path(path_name): - row = layout.row(align=True) - row.prop(props, path_name) - op_path = row.operator("bim.choose_texture_map_path", text="", icon="FILEBROWSER") - op_clear = row.operator("bim.clear_texture_map_path", text="", icon="X") - op_path.texture_map_prop = op_clear.texture_map_prop = path_name - - layout.separator() - texture_maps = TEXTURE_MAPS_BY_METHODS.get(props.reflectance_method, []) - if not texture_maps: - return - - layout.prop(props, "uv_mode") - layout.label(text="Texture Maps:") - for texture_type in texture_maps: - add_texture_path(STYLE_TEXTURE_PROPS_MAP[texture_type]) diff --git a/src/blenderbim/blenderbim/tool/loader.py b/src/blenderbim/blenderbim/tool/loader.py index dbaeedc8e5..039747c11c 100644 --- a/src/blenderbim/blenderbim/tool/loader.py +++ b/src/blenderbim/blenderbim/tool/loader.py @@ -80,7 +80,7 @@ class Loader(blenderbim.core.tool.Loader): # Transparency was added in IFC4 if transparency := surface_style.get("Transparency", None): alpha = 1 - transparency - blender_material.diffuse_color = surface_style["SurfaceColour"][:3] + (alpha,) + blender_material.diffuse_color = surface_style["SurfaceColour"] + (alpha,) blender_material.use_nodes = False @classmethod @@ -100,36 +100,39 @@ class Loader(blenderbim.core.tool.Loader): if isinstance(surface_style, dict): return surface_style surface_style = surface_style.get_info() - color_to_tuple = lambda x: (x.Red, x.Green, x.Blue, 1) + color_to_tuple = lambda x: (x.Red, x.Green, x.Blue) + + def convert_ifc_color_or_factor(color_or_factor): + if color_or_factor is None: + return + if color_or_factor.is_a("IfcColourRgb"): + return ("IfcColourRgb", color_to_tuple(color_or_factor)) + # IfcNormalisedRatioMeasure + return ("IfcNormalisedRatioMeasure", color_or_factor.wrappedValue) + + # can be only IfcColourRgb if surface_style["SurfaceColour"]: surface_style["SurfaceColour"] = color_to_tuple(surface_style["SurfaceColour"]) - if surface_style.get("DiffuseColour", None) and surface_style["DiffuseColour"].is_a("IfcColourRgb"): - surface_style["DiffuseColour"] = ("IfcColourRgb", color_to_tuple(surface_style["DiffuseColour"])) + if surface_style["type"] == "IfcSurfaceStyleShading": + return surface_style - elif surface_style.get("DiffuseColour", None) and surface_style["DiffuseColour"].is_a( - "IfcNormalisedRatioMeasure" - ): - diffuse_color_value = surface_style["DiffuseColour"].wrappedValue - diffuse_color = [v * diffuse_color_value for v in surface_style["SurfaceColour"][:3]] + [1] - surface_style["DiffuseColour"] = ("IfcNormalisedRatioMeasure", diffuse_color) - else: - surface_style["DiffuseColour"] = None + # IfcSurfaceStyleRendering + # IfcColourOrFactor + surface_style["DiffuseColour"] = convert_ifc_color_or_factor(surface_style["DiffuseColour"]) + surface_style["SpecularColour"] = convert_ifc_color_or_factor(surface_style["SpecularColour"]) - if surface_style.get("SpecularColour", None) and surface_style["SpecularColour"].is_a( - "IfcNormalisedRatioMeasure" - ): - surface_style["SpecularColour"] = surface_style["SpecularColour"].wrappedValue - else: - surface_style["SpecularColour"] = None + if specular_highlight := surface_style["SpecularHighlight"]: + if specular_highlight.is_a("IfcSpecularRoughness"): + surface_style["SpecularHighlight"] = specular_highlight.wrappedValue + else: # discard IfcSpecularExponent value + surface_style["SpecularHighlight"] = None + + # NOTE: IfcSurfaceStyleRendering also has following attributes but we ignore them + # as they're about to get deprecated: + # TransmissionColour, DiffuseTransmissionColour, ReflectionColour - if surface_style.get("SpecularHighlight", None) and surface_style["SpecularHighlight"].is_a( - "IfcSpecularRoughness" - ): - surface_style["SpecularHighlight"] = surface_style["SpecularHighlight"].wrappedValue - else: - surface_style["SpecularHighlight"] = None return surface_style @classmethod @@ -158,20 +161,29 @@ class Loader(blenderbim.core.tool.Loader): print(f'WARNING. Unsupported reflectance method "{reflectance_method}" on style {surface_style}') return + # TODO: reset pins to default values if no values passed if reflectance_method in ["PHYSICAL", "NOTDEFINED"]: blender_material.use_nodes = True cls.restart_material_node_tree(blender_material) bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED") + if surface_style["DiffuseColour"]: color_type, color_value = surface_style["DiffuseColour"] if color_type == "IfcColourRgb": - bsdf.inputs["Base Color"].default_value = color_value - elif color_type == "IfcNormalisedRatioMeasure": - bsdf.inputs["Base Color"].default_value = color_value + bsdf.inputs["Base Color"].default_value = color_value + (1,) + else: # "IfcNormalisedRatioMeasure" + color_value = [v * color_value for v in surface_style["SurfaceColour"]] + bsdf.inputs["Base Color"].default_value = color_value + (1,) + if surface_style["SpecularColour"]: - bsdf.inputs["Metallic"].default_value = surface_style["SpecularColour"] + color_type, color_value = surface_style["SpecularColour"] + if color_type == "IfcNormalisedRatioMeasure": + bsdf.inputs["Metallic"].default_value = color_value + # IfcColourRgb is ignored + if surface_style["SpecularHighlight"]: bsdf.inputs["Roughness"].default_value = surface_style["SpecularHighlight"] + if transparency := surface_style.get("Transparency", None): bsdf.inputs["Alpha"].default_value = 1 - transparency blender_material.blend_method = "BLEND" @@ -204,7 +216,7 @@ class Loader(blenderbim.core.tool.Loader): if surface_style["DiffuseColour"]: color_type, color_value = surface_style["DiffuseColour"] if color_type == "IfcColourRgb": - rgb.outputs[0].default_value = color_value + rgb.outputs[0].default_value = color_value + (1,) @classmethod def create_surface_style_with_textures(cls, blender_material, rendering_style, texture_style): diff --git a/src/blenderbim/blenderbim/tool/style.py b/src/blenderbim/blenderbim/tool/style.py index 271a521067..082e768556 100644 --- a/src/blenderbim/blenderbim/tool/style.py +++ b/src/blenderbim/blenderbim/tool/style.py @@ -22,7 +22,7 @@ import ifcopenshell import blenderbim.core.tool import blenderbim.tool as tool import blenderbim.bim.helper -import os.path +from mathutils import Color # fmt: off TEXTURE_MAPS_BY_METHODS = { @@ -33,19 +33,11 @@ TEXTURE_MAPS_BY_METHODS = { STYLE_PROPS_MAP = { "reflectance_method": "ReflectanceMethod", - "diffuse_color": "DiffuseColour", - "surface_color": "SurfaceColour", + "diffuse_colour": "DiffuseColour", + "surface_colour": "SurfaceColour", "transparency": "Transparency", - "roughness": "SpecularHighlight", - "metallic": "SpecularColour", -} - -STYLE_TEXTURE_PROPS_MAP = { - "EMISSIVE": "emissive_path", - "NORMAL": "normal_path", - "METALLICROUGHNESS": "metallic_roughness_path", - "DIFFUSE": "diffuse_path", - "OCCLUSION": "occlusion_path", + "specular_highlight": "SpecularHighlight", + "specular_colour": "SpecularColour", } @@ -111,42 +103,60 @@ class Style(blenderbim.core.tool.Style): return @classmethod - def get_style_elements(cls, blender_material): - if not blender_material.BIMMaterialProperties.ifc_style_id: - return {} - style = tool.Ifc.get().by_id(blender_material.BIMMaterialProperties.ifc_style_id) + def get_style_elements(cls, blender_material_or_style): + if isinstance(blender_material_or_style, bpy.types.Material): + if not blender_material_or_style.BIMMaterialProperties.ifc_style_id: + return {} + style = tool.Ifc.get().by_id(blender_material_or_style.BIMMaterialProperties.ifc_style_id) + else: + style = blender_material_or_style style_elements = {} for style in style.Styles: style_elements[style.is_a()] = style return style_elements @classmethod - def get_surface_style_from_props(cls, blender_material): - """convert blender style props to ifc props""" + def get_shading_style_data_from_props(cls) -> dict: + """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 = blender_material.BIMStyleProperties + props = bpy.context.scene.BIMStylesProperties + + available_props = props.bl_rna.properties.keys() for prop_blender, prop_ifc in STYLE_PROPS_MAP.items(): - surface_style_data[prop_ifc] = getattr(props, prop_blender) - if surface_style_data["ReflectanceMethod"] == "PHYSICAL" and tool.Ifc.get_schema() != "IFC4X3": - surface_style_data["ReflectanceMethod"] = "NOTDEFINED" - surface_style_data["DiffuseColour"] = ("IfcColourRgb", surface_style_data["DiffuseColour"]) + class_prop_name = f"{prop_blender}_class" + + # get detailed color properties if available + if class_prop_name in available_props: + prop_class = getattr(props, class_prop_name) + if prop_class == "IfcColourRgb": + prop_value = tuple(getattr(props, prop_blender)) + else: # IfcNormalisedRatioMeasure + ratio_prop_name = f"{prop_blender}_ratio" + prop_value = getattr(props, ratio_prop_name) + prop_value = (prop_class, prop_value) + else: + prop_value = getattr(props, prop_blender) + if isinstance(prop_value, Color): + prop_value = tuple(prop_value) + + surface_style_data[prop_ifc] = prop_value return surface_style_data @classmethod - def get_texture_style_from_props(cls, blender_material): - props = blender_material.BIMStyleProperties + def get_texture_style_data_from_props(cls) -> list[dict]: + """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 textures = [] - texture_maps = TEXTURE_MAPS_BY_METHODS[props.reflectance_method] - for prop_mode in texture_maps: - prop_name = STYLE_TEXTURE_PROPS_MAP[prop_mode] - path = getattr(props, prop_name) - if not path: + for texture in props.textures: + if not texture.path: continue texture_data = { - "Mode": prop_mode, + "Mode": texture.mode, "type": "IfcImageTexture", - "URLReference": tool.Blender.blender_path_to_posix(path), + "URLReference": texture.path, "uv_mode": props.uv_mode, } textures.append(texture_data) @@ -154,54 +164,70 @@ class Style(blenderbim.core.tool.Style): return textures @classmethod - def set_surface_style_props(cls, blender_material): - """set blender style props based on current surface material""" - props = blender_material.BIMStyleProperties + def set_surface_style_props(cls): + """set blender style props based on currently edited IfcSurfaceStyle, + reset unrelated props to default values""" + + props = bpy.context.scene.BIMStylesProperties + 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 props["update_graph"] = False - style_elements = tool.Style.get_style_elements(blender_material) + style_elements = tool.Style.get_style_elements(style) surface_style = style_elements.get("IfcSurfaceStyleRendering", None) + if surface_style is None: + surface_style = style_elements.get("IfcSurfaceStyleShading", None) + style_data = tool.Loader.surface_style_to_dict(surface_style) if surface_style else {} texture_style = style_elements.get("IfcSurfaceStyleWithTextures", None) - # in case we have just IfcSurfaceStyleShading - if not surface_style: - return - - style_data = tool.Loader.surface_style_to_dict(surface_style) - if style_data["ReflectanceMethod"] == "NOTDEFINED": - style_data["ReflectanceMethod"] = "PHYSICAL" - diffuse_color = style_data["DiffuseColour"] - style_data["DiffuseColour"] = diffuse_color[1] if diffuse_color else None - - for prop_blender, prop_ifc in STYLE_PROPS_MAP.items(): - prop_value = style_data[prop_ifc] + def set_prop(prop_blender, prop_value): if prop_value is None: prop_value = tool.Blender.get_blender_prop_default_value(props, prop_blender) setattr(props, prop_blender, prop_value) - texture_maps = TEXTURE_MAPS_BY_METHODS[style_data["ReflectanceMethod"]] - unused_texture_maps = list(STYLE_TEXTURE_PROPS_MAP.keys()) + available_props = props.bl_rna.properties.keys() + # fallback value for reflectance method + if style_data.get("ReflectanceMethod", None) is None: + style_data["ReflectanceMethod"] = "NOTDEFINED" + for prop_blender, prop_ifc in STYLE_PROPS_MAP.items(): + prop_value = style_data.get(prop_ifc, None) + is_null = prop_value is None + + # set null property if available + null_prop_name = f"is_{prop_blender}_null" + if null_prop_name in available_props: + set_prop(null_prop_name, is_null) + + # set detailed color properties if available + class_prop_name = f"{prop_blender}_class" + if class_prop_name in available_props: + prop_class, prop_value = prop_value or (None, None) + # set class enum + set_prop(class_prop_name, prop_class) + # set prop value + ratio_prop_name = f"{prop_blender}_ratio" + if prop_class == "IfcColourRgb": + set_prop(prop_blender, prop_value) + set_prop(ratio_prop_name, None) + else: # IfcNormalisedRatioMeasure + set_prop(ratio_prop_name, prop_value) + set_prop(prop_blender, None) + continue + + set_prop(prop_blender, prop_value) uv_mode = None + props.textures.clear() if texture_style: for texture in texture_style.Textures: + # we use surface_texture_to_dict as it calculates uv_mode texture_data = tool.Loader.surface_texture_to_dict(texture) + texture_prop = props.textures.add() + texture_prop.mode = texture_data["Mode"] + texture_prop.path = texture_data["URLReference"] uv_mode = texture_data["uv_mode"] - if texture.Mode not in texture_maps: - print(f"WARNING. Unsupported texture mode: {texture.Mode}. Supported maps: {texture_maps}") - continue - prop_blender = STYLE_TEXTURE_PROPS_MAP.get(texture.Mode, None) - setattr(props, prop_blender, texture.URLReference) - unused_texture_maps.remove(texture.Mode) props.uv_mode = uv_mode if uv_mode else "UV" - - # clear empty texture fields - for texture_mode in unused_texture_maps: - prop_blender = STYLE_TEXTURE_PROPS_MAP[texture_mode] - setattr(props, prop_blender, "") - props["update_graph"] = prev_update_graph_value @classmethod diff --git a/src/blenderbim/test/bim/feature/style.feature b/src/blenderbim/test/bim/feature/style.feature index 161c39d167..478b65624f 100644 --- a/src/blenderbim/test/bim/feature/style.feature +++ b/src/blenderbim/test/bim/feature/style.feature @@ -73,7 +73,7 @@ Scenario: Disable editing styles And I add a material And I press "bim.add_style" And I press "bim.load_styles(style_type='IfcSurfaceStyle')" - When I press "bim.disable_editing_style" + When I press "bim.disable_editing_styles" Then nothing happens Scenario: Select by style diff --git a/src/blenderbim/test/tool/test_loader.py b/src/blenderbim/test/tool/test_loader.py index 3ea3b59a5d..ce870d287e 100644 --- a/src/blenderbim/test/tool/test_loader.py +++ b/src/blenderbim/test/tool/test_loader.py @@ -48,11 +48,11 @@ class TestCreatingStyles(NewFile): style_data = { "ReflectanceMethod": "NOTDEFINED", - "DiffuseColour": ("IfcColourRgb", (0.5, 0.5, 0.5, 1.0)), - "SurfaceColour": (0.3, 0.3, 0.3, 1.0), + "DiffuseColour": ("IfcColourRgb", (0.5, 0.5, 0.5)), + "SurfaceColour": (0.3, 0.3, 0.3), "Transparency": 0.3, "SpecularHighlight": 0.4, - "SpecularColour": 0.03, + "SpecularColour": ("IfcNormalisedRatioMeasure", 0.03), } texture_data = [ { @@ -71,12 +71,13 @@ class TestCreatingStyles(NewFile): bsdf = tool.Blender.get_material_node(material, "BSDF_PRINCIPLED") alpha = 1 - style_data["Transparency"] - diffuse_color = style_data["SurfaceColour"][:3] + (alpha,) + diffuse_color = style_data["SurfaceColour"] + (alpha,) assert np.allclose(material.diffuse_color[:], diffuse_color) - assert np.allclose(bsdf.inputs["Base Color"].default_value[:], style_data["DiffuseColour"][1]) + base_color = style_data["DiffuseColour"][1] + (1.0,) + assert np.allclose(bsdf.inputs["Base Color"].default_value[:], base_color) assert np.isclose(bsdf.inputs["Alpha"].default_value, alpha) assert np.isclose(bsdf.inputs["Roughness"].default_value, style_data["SpecularHighlight"]) - assert np.isclose(bsdf.inputs["Metallic"].default_value, style_data["SpecularColour"]) + assert np.isclose(bsdf.inputs["Metallic"].default_value, style_data["SpecularColour"][1]) image_node = tool.Blender.get_material_node(material, "TEX_IMAGE") assert image_node.outputs["Color"].links[0].to_socket.name == "Base Color"