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
This commit is contained in:
Andrej730
2023-12-07 15:25:20 +05:00
parent d0c68d3f5b
commit fa31e10a77
9 changed files with 425 additions and 453 deletions
@@ -23,8 +23,9 @@ classes = (
operator.ActivateExternalStyle, operator.ActivateExternalStyle,
operator.AddPresentationStyle, operator.AddPresentationStyle,
operator.AddStyle, operator.AddStyle,
operator.AddSurfaceTexture,
operator.BrowseExternalStyle, operator.BrowseExternalStyle,
operator.ClearTextureMapPath, operator.RemoveTextureMap,
operator.ChooseTextureMapPath, operator.ChooseTextureMapPath,
operator.DisableAddingPresentationStyle, operator.DisableAddingPresentationStyle,
operator.DisableEditingExternalStyle, operator.DisableEditingExternalStyle,
@@ -46,6 +47,7 @@ classes = (
operator.UpdateStyleColours, operator.UpdateStyleColours,
operator.UpdateStyleTextures, operator.UpdateStyleTextures,
prop.Style, prop.Style,
prop.Texture,
prop.BIMStylesProperties, prop.BIMStylesProperties,
prop.BIMStyleProperties, prop.BIMStyleProperties,
ui.BIM_PT_styles, ui.BIM_PT_styles,
@@ -53,7 +55,6 @@ classes = (
ui.BIM_PT_style_attributes, ui.BIM_PT_style_attributes,
ui.BIM_PT_external_style_attributes, ui.BIM_PT_external_style_attributes,
ui.BIM_UL_styles, ui.BIM_UL_styles,
ui.BIM_PT_STYLE_GRAPH,
) )
@@ -34,12 +34,23 @@ class StylesData:
@classmethod @classmethod
def load(cls): def load(cls):
cls.data = { cls.data = {
"styles_to_blender_material_names": cls.styles_to_blender_material_names(),
"style_types": cls.style_types(), "style_types": cls.style_types(),
"total_styles": cls.total_styles(), "total_styles": cls.total_styles(),
"reflectance_methods": cls.reflectance_methods(), "reflectance_methods": cls.reflectance_methods(),
} }
cls.is_loaded = True 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 @classmethod
def reflectance_methods(cls): def reflectance_methods(cls):
declaration = tool.Ifc.schema().declaration_by_name("IfcReflectanceMethodEnum") declaration = tool.Ifc.schema().declaration_by_name("IfcReflectanceMethodEnum")
@@ -24,8 +24,7 @@ import blenderbim.tool as tool
import blenderbim.core.style as core import blenderbim.core.style as core
import ifcopenshell.util.representation import ifcopenshell.util.representation
from pathlib import Path from pathlib import Path
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.style.data import StyleAttributesData
from blenderbim.bim.module.style.data import StylesData, StyleAttributesData
from mathutils import Vector from mathutils import Vector
@@ -33,14 +32,15 @@ class UpdateStyleColours(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.update_style_colours" bl_idname = "bim.update_style_colours"
bl_label = "Save Current Shading Style" bl_label = "Save Current Shading Style"
bl_description = ( 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"} bl_options = {"REGISTER", "UNDO"}
verbose: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) verbose: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
def invoke(self, context, event): 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 # make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.alt: if event.type == "LEFTMOUSE" and event.alt:
self.verbose = True 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) core.update_style_colours(tool.Ifc, tool.Style, obj=mat, verbose=self.verbose)
if self.verbose: if self.verbose:
self.report({"INFO"}, "Check the system console to see saved style properties") 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): class UpdateStyleTextures(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.update_style_textures" bl_idname = "bim.update_style_textures"
bl_label = "Update Style Textures" bl_label = "Update Style Textures"
bl_description = "Update IfcSurfaceStyleWithTextures based on current blender material shading graph"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
@@ -82,6 +82,7 @@ class RemoveStyle(bpy.types.Operator, tool.Ifc.Operator):
class AddStyle(bpy.types.Operator, tool.Ifc.Operator): class AddStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_style" bl_idname = "bim.add_style"
bl_label = "Add Style" bl_label = "Add Style"
bl_description = "Add IfcSurfaceStyle to the active material"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
@@ -120,6 +121,12 @@ class DisableEditingStyle(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
props = bpy.context.scene.BIMStylesProperties 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 props.is_editing_style = 0
@@ -146,19 +153,14 @@ class UpdateCurrentStyle(bpy.types.Operator):
) )
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
update_all: bpy.props.BoolProperty(name="Update All", default=False, options={"SKIP_SAVE"}) update_all: bpy.props.BoolProperty(name="Update All", default=False, options={"SKIP_SAVE"})
style_id: bpy.props.IntProperty(default=0, options={"SKIP_SAVE"})
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
poll = ( if not context.selected_objects:
context.active_object is not None cls.poll_message_set("No objects selected")
and context.active_object.active_material is not None return False
and context.active_object.active_material.BIMMaterialProperties.ifc_style_id != 0 return True
)
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
def invoke(self, context, event): def invoke(self, context, event):
# updating all styles on shift+click # updating all styles on shift+click
@@ -168,16 +170,18 @@ class UpdateCurrentStyle(bpy.types.Operator):
return self.execute(context) return self.execute(context)
def execute(self, 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: if self.update_all:
context.scene.BIMStylesProperties.active_style_type = current_style_type context.scene.BIMStylesProperties.active_style_type = current_style_type
return {"FINISHED"} return {"FINISHED"}
materials = []
for obj in context.selected_objects: for obj in context.selected_objects:
mat = obj.active_material for mat in obj.data.materials:
if mat and mat.BIMMaterialProperties.ifc_style_id != 0: if mat and mat.BIMMaterialProperties.ifc_style_id != 0:
mat.BIMStyleProperties.active_style_type = current_style_type mat.BIMStyleProperties.active_style_type = current_style_type
return {"FINISHED"} return {"FINISHED"}
@@ -231,7 +235,9 @@ class BrowseExternalStyle(bpy.types.Operator):
description="List of objects in the .blend file", description="List of objects in the .blend file",
items=get_data_blocks, 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( directory: bpy.props.StringProperty(
name="Directory", name="Directory",
description="Start file browsing directory", description="Start file browsing directory",
@@ -266,11 +272,11 @@ class BrowseExternalStyle(bpy.types.Operator):
def execute(self, context): def execute(self, context):
if self.data_block_type == "0": 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"} return {"CANCELLED"}
if self.data_block == "": 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"} return {"CANCELLED"}
if not os.path.exists(self.filepath): if not os.path.exists(self.filepath):
@@ -413,10 +419,12 @@ class SelectByStyle(bpy.types.Operator, tool.Ifc.Operator):
class ChooseTextureMapPath(bpy.types.Operator): class ChooseTextureMapPath(bpy.types.Operator):
bl_idname = "bim.choose_texture_map_path" bl_idname = "bim.choose_texture_map_path"
bl_label = "Choose Texture Map Path" bl_label = "Choose Texture Map Path"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO", "INTERNAL"}
texture_map_prop: bpy.props.StringProperty(default="") 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( filepath: bpy.props.StringProperty(
name="File Path", description="Filepath used to import from", maxlen=1024, default="", subtype="FILE_PATH" 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) context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"} 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): def execute(self, context):
if not self.texture_map_prop: if self.texture_map_index < 0:
self.report({"ERROR"}, "Provide a texture map") self.report({"ERROR"}, "Provide a texture map index")
return {"CANCELLED"} return {"CANCELLED"}
abs_path = Path(self.filepath) abs_path = Path(self.filepath)
@@ -445,30 +446,26 @@ class ChooseTextureMapPath(bpy.types.Operator):
else: else:
image_filepath = abs_path image_filepath = abs_path
props = context.material.BIMStyleProperties texture = context.scene.BIMStylesProperties.textures[self.texture_map_index]
setattr(props, self.texture_map_prop, image_filepath.as_posix()) texture.path = image_filepath.as_posix()
return {"FINISHED"} return {"FINISHED"}
class ClearTextureMapPath(bpy.types.Operator): class RemoveTextureMap(bpy.types.Operator):
bl_idname = "bim.clear_texture_map_path" bl_idname = "bim.remove_texture_map"
bl_label = "Clear Texture Map Path" bl_label = "Remove Texture Map"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO", "INTERNAL"}
texture_map_prop: bpy.props.StringProperty(default="") texture_map_index: bpy.props.IntProperty(default=-1, options={"SKIP_SAVE"})
@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): def execute(self, context):
if not self.texture_map_prop: if self.texture_map_index < 0:
self.report({"ERROR"}, "Provide a texture map") self.report({"ERROR"}, "Provide a texture map index")
return {"CANCELLED"} 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"} return {"FINISHED"}
@@ -546,74 +543,15 @@ class EnableEditingSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
props = bpy.context.scene.BIMStylesProperties props = bpy.context.scene.BIMStylesProperties
style = tool.Ifc.get().by_id(self.style) 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_style = self.style
props.is_editing_class = self.ifc_class 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): 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.props = bpy.context.scene.BIMStylesProperties
self.style = tool.Ifc.get().by_id(self.props.is_editing_style) self.style = tool.Ifc.get().by_id(self.props.is_editing_style)
self.surface_style = None style_elements = tool.Style.get_style_elements(self.style)
self.shading_style = None self.surface_style = style_elements.get(self.props.is_editing_class, None)
self.rendering_style = None self.shading_style = style_elements.get("IfcSurfaceStyleShading", None)
for style2 in self.style.Styles: self.rendering_style = style_elements.get("IfcSurfaceStyleRendering", None)
if style2.is_a() == self.props.is_editing_class: self.texture_style = style_elements.get("IfcSurfaceStyleWithTextures", None)
self.surface_style = style2
if style2.is_a() == "IfcSurfaceStyleShading":
self.shading_style = style2
if style2.is_a() == "IfcSurfaceStyleRendering":
self.rendering_style = style2
if self.surface_style: if self.surface_style:
self.edit_existing_style() self.edit_existing_style()
@@ -665,8 +598,27 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
attributes=self.get_rendering_attributes(), attributes=self.get_rendering_attributes(),
) )
tool.Loader.create_surface_style_rendering(material, self.surface_style) 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": elif self.surface_style.is_a() == "IfcExternallyDefinedSurfaceStyle":
surface_style = ifcopenshell.api.run( ifcopenshell.api.run(
"style.edit_surface_style", "style.edit_surface_style",
tool.Ifc.get(), tool.Ifc.get(),
style=self.surface_style, style=self.surface_style,
@@ -693,6 +645,30 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
attributes=self.get_rendering_attributes(), attributes=self.get_rendering_attributes(),
) )
tool.Loader.create_surface_style_rendering(material, surface_style) 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": elif self.props.is_editing_class == "IfcExternallyDefinedSurfaceStyle":
surface_style = ifcopenshell.api.run( surface_style = ifcopenshell.api.run(
"style.add_surface_style", "style.add_surface_style",
@@ -726,7 +702,7 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
if self.props.is_specular_highlight_null: if self.props.is_specular_highlight_null:
specular_highlight = None specular_highlight = None
else: else:
specular_highlight = {"SpecularRoughness": self.props.specular_highlight} specular_highlight = {"IfcSpecularRoughness": self.props.specular_highlight}
return { return {
"SurfaceColour": self.color_to_dict(self.props.surface_colour), "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]} 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): class SaveUVToStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.save_uv_to_style" bl_idname = "bim.save_uv_to_style"
bl_label = "Save UV To Style" bl_label = "Save UV To Style"
+100 -135
View File
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
from blenderbim.bim.ifc import IfcStore import blenderbim.tool as tool
from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.style.data import StylesData from blenderbim.bim.module.style.data import StylesData
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
@@ -31,7 +31,6 @@ from bpy.props import (
FloatVectorProperty, FloatVectorProperty,
CollectionProperty, CollectionProperty,
) )
import blenderbim.tool as tool
def get_style_types(self, context): 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) 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): class BIMStylesProperties(PropertyGroup):
is_adding: BoolProperty(name="Is Adding") is_adding: BoolProperty(name="Is Adding")
is_editing: BoolProperty(name="Is Editing") is_editing: BoolProperty(name="Is Editing")
@@ -98,31 +136,81 @@ class BIMStylesProperties(PropertyGroup):
name="Surface Style Class", name="Surface Style Class",
default="IfcSurfaceStyleShading", default="IfcSurfaceStyleShading",
) )
surface_colour: bpy.props.FloatVectorProperty( update_graph: BoolProperty(
name="Surface Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3 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") is_diffuse_colour_null: BoolProperty(name="Is Null")
diffuse_colour_class: EnumProperty( diffuse_colour_class: EnumProperty(
items=[(x, x, "") for x in ("IfcColourRgb", "IfcNormalisedRatioMeasure")], items=[(x, x, "") for x in ("IfcColourRgb", "IfcNormalisedRatioMeasure")],
name="Diffuse Colour Class", name="Diffuse Colour Class",
update=update_shader_graph,
) )
diffuse_colour: bpy.props.FloatVectorProperty( 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") is_specular_colour_null: BoolProperty(name="Is Null")
specular_colour_class: EnumProperty( specular_colour_class: EnumProperty(
items=[(x, x, "") for x in ("IfcColourRgb", "IfcNormalisedRatioMeasure")], items=[(x, x, "") for x in ("IfcColourRgb", "IfcNormalisedRatioMeasure")],
name="Specular Colour Class", name="Specular Colour Class",
update=update_shader_graph,
default="IfcNormalisedRatioMeasure",
) )
specular_colour: bpy.props.FloatVectorProperty( 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") 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) specular_highlight: bpy.props.FloatProperty(
reflectance_method: EnumProperty(name="Reflectance Method", items=get_reflectance_methods) 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) styles: CollectionProperty(name="Styles", type=Style)
active_style_index: IntProperty(name="Active Style Index") active_style_index: IntProperty(name="Active Style Index")
active_style_type: EnumProperty( active_style_type: EnumProperty(
@@ -157,54 +245,13 @@ def update_shading_style(self, context):
if rendering_style and texture_style: if rendering_style and texture_style:
tool.Loader.create_surface_style_with_textures(blender_material, rendering_style, 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) 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): class BIMStyleProperties(PropertyGroup):
# TODO: remove, as attributes already moved to styles ui
attributes: CollectionProperty(name="Attributes", type=Attribute) attributes: CollectionProperty(name="Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing") is_editing: BoolProperty(name="Is Editing")
external_style_attributes: CollectionProperty(name="External Style Attributes", type=Attribute) external_style_attributes: CollectionProperty(name="External Style Attributes", type=Attribute)
is_editing_external_style: BoolProperty(name="Is Editing External Style") is_editing_external_style: BoolProperty(name="Is Editing External Style")
@@ -215,85 +262,3 @@ class BIMStyleProperties(PropertyGroup):
default="Shading", default="Shading",
update=update_shading_style, 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,
)
@@ -18,11 +18,11 @@
import bpy import bpy
import blenderbim.bim.helper import blenderbim.bim.helper
import blenderbim.tool as tool
from bpy.types import Panel, UIList from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.style.data import StylesData, StyleAttributesData from blenderbim.bim.module.style.data import StylesData, StyleAttributesData
from bl_ui.properties_material import MaterialButtonsPanel 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): 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") self.layout.template_list("BIM_UL_styles", "", self.props, "styles", self.props, "active_style_index")
# adding a new IfcSurfaceStyle
if self.props.is_adding: if self.props.is_adding:
box = self.layout.box() box = self.layout.box()
row = box.row() 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.add_presentation_style", text="Save New Style", icon="CHECKMARK")
row.operator("bim.disable_adding_presentation_style", text="", icon="CANCEL") 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): if self.props.styles and self.props.active_style_index < len(self.props.styles):
row = self.layout.row(align=True) row = self.layout.row(align=True)
style = self.props.styles[self.props.active_style_index] 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 = row.operator("bim.enable_editing_style", text="Edit Style", icon="GREASEPENCIL")
op.style = style.ifc_definition_id 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.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.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": if self.props.style_type == "IfcSurfaceStyle":
self.layout.label(text="Surface Style Element:") self.layout.label(text="Surface Style Element:")
col = self.layout.column(align=True) col = self.layout.column(align=True)
@@ -109,6 +119,7 @@ class BIM_PT_styles(Panel):
op.ifc_class = "IfcExternallyDefinedSurfaceStyle" op.ifc_class = "IfcExternallyDefinedSurfaceStyle"
op.style = style.ifc_definition_id op.style = style.ifc_definition_id
# display style elements props during edit
if self.props.is_editing_style: if self.props.is_editing_style:
if self.props.is_editing_class == "IfcSurfaceStyle": if self.props.is_editing_class == "IfcSurfaceStyle":
blenderbim.bim.helper.draw_attributes(self.props.attributes, self.layout) blenderbim.bim.helper.draw_attributes(self.props.attributes, self.layout)
@@ -121,8 +132,10 @@ class BIM_PT_styles(Panel):
self.draw_surface_style_rendering() self.draw_surface_style_rendering()
elif self.props.is_editing_class == "IfcExternallyDefinedSurfaceStyle": elif self.props.is_editing_class == "IfcExternallyDefinedSurfaceStyle":
self.draw_externally_defined_surface_style() self.draw_externally_defined_surface_style()
elif self.props.is_editing_class == "IfcSurfaceStyleWithTextures":
self.draw_surface_style_with_textures()
else: 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.") self.layout.label(text=f"{self.props.is_editing_class} UI is not yet supported.")
def draw_surface_style_shading(self): def draw_surface_style_shading(self):
@@ -142,8 +155,12 @@ class BIM_PT_styles(Panel):
row = self.layout.row() row = self.layout.row()
row.prop(self.props, "reflectance_method") 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 = 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="") row.prop(self.props, "diffuse_colour_class", text="")
if self.props.diffuse_colour_class == "IfcColourRgb": if self.props.diffuse_colour_class == "IfcColourRgb":
row.prop(self.props, "diffuse_colour", text="") 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.edit_surface_style", text="Save Rendering Style", icon="CHECKMARK")
row.operator("bim.disable_editing_style", text="", icon="CANCEL") 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): def draw_externally_defined_surface_style(self):
blenderbim.bim.helper.draw_attributes(self.props.external_style_attributes, self.layout) blenderbim.bim.helper.draw_attributes(self.props.external_style_attributes, self.layout)
row = self.layout.row(align=True) row = self.layout.row(align=True)
@@ -199,24 +238,6 @@ class BIM_PT_styles(Panel):
row.operator("bim.disable_editing_style", text="", icon="CANCEL") 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): class BIM_PT_style(MaterialButtonsPanel, Panel):
bl_label = "Style" bl_label = "Style"
bl_idname = "BIM_PT_style" bl_idname = "BIM_PT_style"
@@ -235,11 +256,15 @@ class BIM_PT_style(MaterialButtonsPanel, Panel):
def draw(self, context): def draw(self, context):
mat = context.material mat = context.material
props = mat.BIMMaterialProperties props = mat.BIMMaterialProperties
draw_style_ui(self, context) row = self.layout.row(align=True)
if not props.ifc_style_id: if not props.ifc_style_id:
row.operator("bim.add_style", icon="ADD")
return return
row = self.layout.row(align=True) 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): class BIM_PT_style_attributes(Panel):
@@ -369,66 +394,3 @@ class BIM_UL_styles(UIList):
row2.label(text="", icon="LIGHT_POINT") row2.label(text="", icon="LIGHT_POINT")
elif style.name == "IfcExternallyDefinedSurfaceStyle": elif style.name == "IfcExternallyDefinedSurfaceStyle":
row2.label(text="", icon="APPEND_BLEND") 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])
+41 -29
View File
@@ -80,7 +80,7 @@ class Loader(blenderbim.core.tool.Loader):
# Transparency was added in IFC4 # Transparency was added in IFC4
if transparency := surface_style.get("Transparency", None): if transparency := surface_style.get("Transparency", None):
alpha = 1 - transparency 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 blender_material.use_nodes = False
@classmethod @classmethod
@@ -100,36 +100,39 @@ class Loader(blenderbim.core.tool.Loader):
if isinstance(surface_style, dict): if isinstance(surface_style, dict):
return surface_style return surface_style
surface_style = surface_style.get_info() 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"]: if surface_style["SurfaceColour"]:
surface_style["SurfaceColour"] = color_to_tuple(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"): if surface_style["type"] == "IfcSurfaceStyleShading":
surface_style["DiffuseColour"] = ("IfcColourRgb", color_to_tuple(surface_style["DiffuseColour"])) return surface_style
elif surface_style.get("DiffuseColour", None) and surface_style["DiffuseColour"].is_a( # IfcSurfaceStyleRendering
"IfcNormalisedRatioMeasure" # IfcColourOrFactor
): surface_style["DiffuseColour"] = convert_ifc_color_or_factor(surface_style["DiffuseColour"])
diffuse_color_value = surface_style["DiffuseColour"].wrappedValue surface_style["SpecularColour"] = convert_ifc_color_or_factor(surface_style["SpecularColour"])
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
if surface_style.get("SpecularColour", None) and surface_style["SpecularColour"].is_a( if specular_highlight := surface_style["SpecularHighlight"]:
"IfcNormalisedRatioMeasure" if specular_highlight.is_a("IfcSpecularRoughness"):
): surface_style["SpecularHighlight"] = specular_highlight.wrappedValue
surface_style["SpecularColour"] = surface_style["SpecularColour"].wrappedValue else: # discard IfcSpecularExponent value
else: surface_style["SpecularHighlight"] = None
surface_style["SpecularColour"] = 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 return surface_style
@classmethod @classmethod
@@ -158,20 +161,29 @@ class Loader(blenderbim.core.tool.Loader):
print(f'WARNING. Unsupported reflectance method "{reflectance_method}" on style {surface_style}') print(f'WARNING. Unsupported reflectance method "{reflectance_method}" on style {surface_style}')
return return
# TODO: reset pins to default values if no values passed
if reflectance_method in ["PHYSICAL", "NOTDEFINED"]: if reflectance_method in ["PHYSICAL", "NOTDEFINED"]:
blender_material.use_nodes = True blender_material.use_nodes = True
cls.restart_material_node_tree(blender_material) cls.restart_material_node_tree(blender_material)
bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED") bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED")
if surface_style["DiffuseColour"]: if surface_style["DiffuseColour"]:
color_type, color_value = surface_style["DiffuseColour"] color_type, color_value = surface_style["DiffuseColour"]
if color_type == "IfcColourRgb": if color_type == "IfcColourRgb":
bsdf.inputs["Base Color"].default_value = color_value bsdf.inputs["Base Color"].default_value = color_value + (1,)
elif color_type == "IfcNormalisedRatioMeasure": else: # "IfcNormalisedRatioMeasure"
bsdf.inputs["Base Color"].default_value = color_value color_value = [v * color_value for v in surface_style["SurfaceColour"]]
bsdf.inputs["Base Color"].default_value = color_value + (1,)
if surface_style["SpecularColour"]: 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"]: if surface_style["SpecularHighlight"]:
bsdf.inputs["Roughness"].default_value = surface_style["SpecularHighlight"] bsdf.inputs["Roughness"].default_value = surface_style["SpecularHighlight"]
if transparency := surface_style.get("Transparency", None): if transparency := surface_style.get("Transparency", None):
bsdf.inputs["Alpha"].default_value = 1 - transparency bsdf.inputs["Alpha"].default_value = 1 - transparency
blender_material.blend_method = "BLEND" blender_material.blend_method = "BLEND"
@@ -204,7 +216,7 @@ class Loader(blenderbim.core.tool.Loader):
if surface_style["DiffuseColour"]: if surface_style["DiffuseColour"]:
color_type, color_value = surface_style["DiffuseColour"] color_type, color_value = surface_style["DiffuseColour"]
if color_type == "IfcColourRgb": if color_type == "IfcColourRgb":
rgb.outputs[0].default_value = color_value rgb.outputs[0].default_value = color_value + (1,)
@classmethod @classmethod
def create_surface_style_with_textures(cls, blender_material, rendering_style, texture_style): def create_surface_style_with_textures(cls, blender_material, rendering_style, texture_style):
+89 -63
View File
@@ -22,7 +22,7 @@ import ifcopenshell
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.helper import blenderbim.bim.helper
import os.path from mathutils import Color
# fmt: off # fmt: off
TEXTURE_MAPS_BY_METHODS = { TEXTURE_MAPS_BY_METHODS = {
@@ -33,19 +33,11 @@ TEXTURE_MAPS_BY_METHODS = {
STYLE_PROPS_MAP = { STYLE_PROPS_MAP = {
"reflectance_method": "ReflectanceMethod", "reflectance_method": "ReflectanceMethod",
"diffuse_color": "DiffuseColour", "diffuse_colour": "DiffuseColour",
"surface_color": "SurfaceColour", "surface_colour": "SurfaceColour",
"transparency": "Transparency", "transparency": "Transparency",
"roughness": "SpecularHighlight", "specular_highlight": "SpecularHighlight",
"metallic": "SpecularColour", "specular_colour": "SpecularColour",
}
STYLE_TEXTURE_PROPS_MAP = {
"EMISSIVE": "emissive_path",
"NORMAL": "normal_path",
"METALLICROUGHNESS": "metallic_roughness_path",
"DIFFUSE": "diffuse_path",
"OCCLUSION": "occlusion_path",
} }
@@ -111,42 +103,60 @@ class Style(blenderbim.core.tool.Style):
return return
@classmethod @classmethod
def get_style_elements(cls, blender_material): def get_style_elements(cls, blender_material_or_style):
if not blender_material.BIMMaterialProperties.ifc_style_id: if isinstance(blender_material_or_style, bpy.types.Material):
return {} if not blender_material_or_style.BIMMaterialProperties.ifc_style_id:
style = tool.Ifc.get().by_id(blender_material.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 = {} style_elements = {}
for style in style.Styles: for style in style.Styles:
style_elements[style.is_a()] = style style_elements[style.is_a()] = style
return style_elements return style_elements
@classmethod @classmethod
def get_surface_style_from_props(cls, blender_material): def get_shading_style_data_from_props(cls) -> dict:
"""convert blender style props to ifc props""" """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() 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(): for prop_blender, prop_ifc in STYLE_PROPS_MAP.items():
surface_style_data[prop_ifc] = getattr(props, prop_blender) class_prop_name = f"{prop_blender}_class"
if surface_style_data["ReflectanceMethod"] == "PHYSICAL" and tool.Ifc.get_schema() != "IFC4X3":
surface_style_data["ReflectanceMethod"] = "NOTDEFINED" # get detailed color properties if available
surface_style_data["DiffuseColour"] = ("IfcColourRgb", surface_style_data["DiffuseColour"]) 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 return surface_style_data
@classmethod @classmethod
def get_texture_style_from_props(cls, blender_material): def get_texture_style_data_from_props(cls) -> list[dict]:
props = blender_material.BIMStyleProperties """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 = [] textures = []
texture_maps = TEXTURE_MAPS_BY_METHODS[props.reflectance_method] for texture in props.textures:
for prop_mode in texture_maps: if not texture.path:
prop_name = STYLE_TEXTURE_PROPS_MAP[prop_mode]
path = getattr(props, prop_name)
if not path:
continue continue
texture_data = { texture_data = {
"Mode": prop_mode, "Mode": texture.mode,
"type": "IfcImageTexture", "type": "IfcImageTexture",
"URLReference": tool.Blender.blender_path_to_posix(path), "URLReference": texture.path,
"uv_mode": props.uv_mode, "uv_mode": props.uv_mode,
} }
textures.append(texture_data) textures.append(texture_data)
@@ -154,54 +164,70 @@ class Style(blenderbim.core.tool.Style):
return textures return textures
@classmethod @classmethod
def set_surface_style_props(cls, blender_material): def set_surface_style_props(cls):
"""set blender style props based on current surface material""" """set blender style props based on currently edited IfcSurfaceStyle,
props = blender_material.BIMStyleProperties 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 # make sure won't be updating while we changing it
prev_update_graph_value = props.update_graph prev_update_graph_value = props.update_graph
props["update_graph"] = False 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) 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) texture_style = style_elements.get("IfcSurfaceStyleWithTextures", None)
# in case we have just IfcSurfaceStyleShading def set_prop(prop_blender, prop_value):
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]
if prop_value is None: if prop_value is None:
prop_value = tool.Blender.get_blender_prop_default_value(props, prop_blender) prop_value = tool.Blender.get_blender_prop_default_value(props, prop_blender)
setattr(props, prop_blender, prop_value) setattr(props, prop_blender, prop_value)
texture_maps = TEXTURE_MAPS_BY_METHODS[style_data["ReflectanceMethod"]] available_props = props.bl_rna.properties.keys()
unused_texture_maps = list(STYLE_TEXTURE_PROPS_MAP.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 uv_mode = None
props.textures.clear()
if texture_style: if texture_style:
for texture in texture_style.Textures: 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_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"] 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" 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 props["update_graph"] = prev_update_graph_value
@classmethod @classmethod
@@ -73,7 +73,7 @@ Scenario: Disable editing styles
And I add a material And I add a material
And I press "bim.add_style" And I press "bim.add_style"
And I press "bim.load_styles(style_type='IfcSurfaceStyle')" 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 Then nothing happens
Scenario: Select by style Scenario: Select by style
+7 -6
View File
@@ -48,11 +48,11 @@ class TestCreatingStyles(NewFile):
style_data = { style_data = {
"ReflectanceMethod": "NOTDEFINED", "ReflectanceMethod": "NOTDEFINED",
"DiffuseColour": ("IfcColourRgb", (0.5, 0.5, 0.5, 1.0)), "DiffuseColour": ("IfcColourRgb", (0.5, 0.5, 0.5)),
"SurfaceColour": (0.3, 0.3, 0.3, 1.0), "SurfaceColour": (0.3, 0.3, 0.3),
"Transparency": 0.3, "Transparency": 0.3,
"SpecularHighlight": 0.4, "SpecularHighlight": 0.4,
"SpecularColour": 0.03, "SpecularColour": ("IfcNormalisedRatioMeasure", 0.03),
} }
texture_data = [ texture_data = [
{ {
@@ -71,12 +71,13 @@ class TestCreatingStyles(NewFile):
bsdf = tool.Blender.get_material_node(material, "BSDF_PRINCIPLED") bsdf = tool.Blender.get_material_node(material, "BSDF_PRINCIPLED")
alpha = 1 - style_data["Transparency"] 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(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["Alpha"].default_value, alpha)
assert np.isclose(bsdf.inputs["Roughness"].default_value, style_data["SpecularHighlight"]) 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") image_node = tool.Blender.get_material_node(material, "TEX_IMAGE")
assert image_node.outputs["Color"].links[0].to_socket.name == "Base Color" assert image_node.outputs["Color"].links[0].to_socket.name == "Base Color"