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"