External Styles

Added support for external blender styles that will be saved to ifc.
What it basically means that you can append material from external .blend file as a style that can be loaded in your blender material anytime.

1) External style can be added in Material Tab
https://i.imgur.com/BYfesi5.png

Note that you select material name at the right side of file selecting panel:
https://i.imgur.com/mvqaaod.png

2) After adding external style you can switch between external and other surface styles:
https://i.imgur.com/YtCz8an.png

3) There is also option to switch styles to External for all objects in the scene:
https://i.imgur.com/u3zTcX9.png

Some other changes:
- some ui adjustments
- moved functions that create blender shaders to tool.Loader (they also reset shader graph now so it's possible to use them on non-default blender materials)
- some new tools for copying node graph from one material to another, appending data blocks
- new operator for browsing external styles can be reused later to append other data blocks
This commit is contained in:
Andrej730
2023-06-09 18:39:50 +05:00
parent ed990c3e83
commit 04fe2bdf2e
11 changed files with 651 additions and 179 deletions
+3 -158
View File
@@ -1490,173 +1490,18 @@ class IfcImporter:
for surface_style in style.Styles: for surface_style in style.Styles:
if surface_style.is_a() == "IfcSurfaceStyleShading": if surface_style.is_a() == "IfcSurfaceStyleShading":
self.create_surface_style_shading(blender_material, surface_style) tool.Loader.create_surface_style_shading(blender_material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleRendering"): elif surface_style.is_a("IfcSurfaceStyleRendering"):
rendering_style = surface_style rendering_style = surface_style
self.create_surface_style_rendering(blender_material, surface_style) tool.Loader.create_surface_style_rendering(blender_material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleWithTextures"): elif surface_style.is_a("IfcSurfaceStyleWithTextures"):
texture_style = surface_style texture_style = surface_style
if rendering_style and texture_style: if rendering_style and texture_style:
self.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.record_shading(blender_material) tool.Style.record_shading(blender_material)
def create_surface_style_shading(self, blender_material, surface_style):
alpha = 1.0
# Transparency was added in IFC4
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
alpha = 1 - surface_style.Transparency
blender_material.diffuse_color = (
surface_style.SurfaceColour.Red,
surface_style.SurfaceColour.Green,
surface_style.SurfaceColour.Blue,
alpha,
)
def create_surface_style_rendering(self, blender_material, surface_style):
self.create_surface_style_shading(blender_material, surface_style)
if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
blender_material.use_nodes = True
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
if surface_style.DiffuseColour:
if surface_style.DiffuseColour.is_a("IfcColourRgb"):
bsdf.inputs["Base Color"].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
elif surface_style.DiffuseColour.is_a("IfcNormalisedRatioMeasure"):
bsdf.inputs["Base Color"].default_value = (
surface_style.SurfaceColour.Red * surface_style.DiffuseColour.wrappedValue,
surface_style.SurfaceColour.Green * surface_style.DiffuseColour.wrappedValue,
surface_style.SurfaceColour.Blue * surface_style.DiffuseColour.wrappedValue,
1,
)
if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"):
bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue
if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
bsdf.inputs["Roughness"].default_value = surface_style.SpecularHighlight.wrappedValue
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
bsdf.inputs["Alpha"].default_value = 1 - surface_style.Transparency
blender_material.blend_method = "BLEND"
elif surface_style.ReflectanceMethod == "FLAT":
blender_material.use_nodes = True
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader")
mix.location = bsdf.location
blender_material.node_tree.links.new(mix.outputs[0], output.inputs["Surface"])
blender_material.node_tree.nodes.remove(bsdf)
lightpath = blender_material.node_tree.nodes.new(type="ShaderNodeLightPath")
lightpath.location = mix.location - mathutils.Vector((200, -200))
blender_material.node_tree.links.new(lightpath.outputs[0], mix.inputs[0])
bsdf = blender_material.node_tree.nodes.new(type="ShaderNodeBsdfTransparent")
bsdf.location = mix.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(bsdf.outputs[0], mix.inputs[1])
rgb = blender_material.node_tree.nodes.new(type="ShaderNodeRGB")
rgb.location = mix.location - mathutils.Vector((200, 200))
blender_material.node_tree.links.new(rgb.outputs[0], mix.inputs[2])
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
rgb.outputs[0].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
def create_surface_style_with_textures(self, blender_material, rendering_style, texture_style):
for texture in texture_style.Textures:
mode = getattr(texture, "Mode", None)
node = None
if texture.is_a("IfcImageTexture"):
image_url = texture.URLReference
if not os.path.abspath(texture.URLReference) and tool.Ifc.get_path():
image_url = os.path.join(os.path.dirname(tool.Ifc.get_path()), texture.URLReference)
if rendering_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
if mode == "NORMAL":
normalmap = blender_material.node_tree.nodes.new(type="ShaderNodeNormalMap")
normalmap.location = bsdf.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(normalmap.outputs[0], bsdf.inputs["Normal"])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = normalmap.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
image.colorspace_settings.name = "Non-Color"
node.image = image
blender_material.node_tree.links.new(node.outputs[0], normalmap.inputs["Color"])
elif mode == "EMISSIVE":
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
add = blender_material.node_tree.nodes.new(type="ShaderNodeAddShader")
add.location = bsdf.location + mathutils.Vector((200, 0))
blender_material.node_tree.links.new(bsdf.outputs[0], add.inputs[1])
blender_material.node_tree.links.new(add.outputs[0], output.inputs[0])
emission = blender_material.node_tree.nodes.new(type="ShaderNodeEmission")
emission.location = add.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(emission.outputs[0], add.inputs[0])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = emission.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], emission.inputs[0])
elif mode == "METALLICROUGHNESS":
separate = blender_material.node_tree.nodes.new(type="ShaderNodeSeparateRGB")
separate.location = bsdf.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(separate.outputs[1], bsdf.inputs["Roughness"])
blender_material.node_tree.links.new(separate.outputs[2], bsdf.inputs["Metallic"])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = separate.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
image.colorspace_settings.name = "Non-Color"
node.image = image
blender_material.node_tree.links.new(node.outputs[0], separate.inputs[0])
elif mode == "OCCLUSION":
# TODO work out how to implement glTF settings here
# https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html
pass
elif mode == "DIFFUSE":
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = bsdf.location - mathutils.Vector((400, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs["Base Color"])
blender_material.node_tree.links.new(node.outputs[1], bsdf.inputs["Alpha"])
blender_material.blend_method = "BLEND"
elif rendering_style.ReflectanceMethod == "FLAT":
bsdf = blender_material.node_tree.nodes["Mix Shader"]
if mode == "EMISSIVE":
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = bsdf.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs[2])
if node and getattr(texture, "IsMappedBy", None):
coordinates = texture.IsMappedBy[0]
coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord")
coord.location = node.location - mathutils.Vector((200, 0))
if coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD":
blender_material.node_tree.links.new(coord.outputs["Generated"], node.inputs["Vector"])
elif coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD-EYE":
blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"])
else:
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
def place_objects_in_collections(self): def place_objects_in_collections(self):
for ifc_definition_id, obj in self.added_data.items(): for ifc_definition_id, obj in self.added_data.items():
if isinstance(obj, bpy.types.Object): if isinstance(obj, bpy.types.Object):
@@ -30,7 +30,8 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
props = context.scene.BIMMiscProperties props = context.scene.BIMMiscProperties
row = layout.row()
row.prop(context.scene.BIMStylesProperties, "active_style_type", icon="SHADING_RENDERED", text="")
row = layout.split(factor=0.2, align=True) row = layout.split(factor=0.2, align=True)
row.prop(props, "override_colour", text="") row.prop(props, "override_colour", text="")
row.operator("bim.set_override_colour") row.operator("bim.set_override_colour")
@@ -21,10 +21,15 @@ from . import ui, prop, operator
classes = ( classes = (
operator.AddStyle, operator.AddStyle,
operator.DisableEditingStyle,
operator.DisableEditingStyles,
operator.EditStyle,
operator.EnableEditingStyle, operator.EnableEditingStyle,
operator.DisableEditingStyle,
operator.EditStyle,
operator.EnableEditingExternalStyle,
operator.DisableEditingExternalStyle,
operator.EditExternalStyle,
operator.DisableEditingStyles,
operator.BrowseExternalStyle,
operator.ActivateExternalStyle,
operator.LoadStyles, operator.LoadStyles,
operator.RemoveStyle, operator.RemoveStyle,
operator.SelectByStyle, operator.SelectByStyle,
@@ -37,6 +42,7 @@ classes = (
ui.BIM_PT_styles, ui.BIM_PT_styles,
ui.BIM_PT_style, ui.BIM_PT_style,
ui.BIM_PT_style_attributes, ui.BIM_PT_style_attributes,
ui.BIM_PT_external_style_attributes,
ui.BIM_UL_styles, ui.BIM_UL_styles,
) )
@@ -56,7 +56,12 @@ class StyleAttributesData:
@classmethod @classmethod
def load(cls): def load(cls):
cls.data = {"ifc_style_id": cls.ifc_style_id(), "attributes": cls.get_attributes()} cls.data = {
"ifc_style_id": cls.ifc_style_id(),
"attributes": cls.get_attributes(),
"style_elements": cls.get_style_elements(),
}
cls.data["external_style_attributes"] = cls.get_external_style_attributes()
cls.is_loaded = True cls.is_loaded = True
@classmethod @classmethod
@@ -72,3 +77,19 @@ class StyleAttributesData:
continue continue
results.append({"name": name, "value": str(value)}) results.append({"name": name, "value": str(value)})
return results return results
@classmethod
def get_style_elements(cls):
return tool.Style.get_style_elements(bpy.context.active_object.active_material)
@classmethod
def get_external_style_attributes(cls):
external_style = cls.data["style_elements"].get("IfcExternallyDefinedSurfaceStyle", None)
if not external_style:
return None
results = []
for name, value in external_style.get_info().items():
if name in ["id", "type"]:
continue
results.append({"name": name, "value": str(value)})
return results
@@ -22,11 +22,15 @@ 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 blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.style.data import StylesData, StyleAttributesData
from pathlib import Path
import os
class UpdateStyleColours(bpy.types.Operator, tool.Ifc.Operator): class UpdateStyleColours(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.update_style_colours" bl_idname = "bim.update_style_colours"
bl_label = "Update Style Colours" bl_label = "Save Current Shading Style"
bl_description = "Save current style values to IfcSurfaceStyleShading"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
@@ -103,6 +107,200 @@ class EditStyle(bpy.types.Operator, tool.Ifc.Operator):
core.edit_style(tool.Ifc, tool.Style, obj=context.active_object.active_material) core.edit_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
class BrowseExternalStyle(bpy.types.Operator):
bl_idname = "bim.browse_external_style"
bl_label = "Browse External Style"
bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(
name="File Path", description="Filepath used to import from", maxlen=1024, default="", subtype="FILE_PATH"
)
filter_glob: bpy.props.StringProperty(
default="*.blend",
options={"HIDDEN"},
)
def get_data_block_types(self, context):
return [("materials", "materials", "materials")]
# NOTE: the code below can be used later when we'll be adding other data-blocks besides materials
l = [("0", "", "")]
SUPPORTED_DATA_BLOCKS = ("materials", "textures", "brushes")
if os.path.exists(self.filepath) and self.filepath.endswith(".blend"):
with bpy.data.libraries.load(self.filepath) as (data_from, data_to):
for data_block_type in dir(data_from):
if data_block_type not in SUPPORTED_DATA_BLOCKS:
continue
data = getattr(data_from, data_block_type)
if data:
item = (data_block_type,) * 3
l.append(item)
return l
def get_data_blocks(self, context):
l = [("", "", "")]
if self.data_block_type != "0" and os.path.exists(self.filepath) and self.filepath.endswith(".blend"):
with bpy.data.libraries.load(self.filepath) as (data_from, data_to):
objects = getattr(data_from, self.data_block_type)
for o in objects:
l.append((o, o, o))
return l
data_block_type: bpy.props.EnumProperty(
name="Data Block Type",
description="List of data blocks in the .blend file",
items=get_data_block_types,
)
data_block: bpy.props.EnumProperty(
name="List of objects in the .blend file",
description="List of objects in the .blend file",
items=get_data_blocks,
)
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
directory: bpy.props.StringProperty(
name="Directory",
description="Start file browsing directory",
default="",
)
def invoke(self, context, event):
mat = context.active_object.active_material
external_style = tool.Style.get_style_elements(mat).get("IfcExternallyDefinedSurfaceStyle", None)
# automatically select previously selected external style in file browser
if external_style and self.filepath == "":
style_path = Path(tool.Ifc.resolve_uri(external_style.Location))
self.directory = str(style_path.parent)
self.filepath = str(style_path)
self.data_block_type, self.data_block = external_style.Identification.split("/")
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
def draw(self, context):
layout = self.layout
layout.label(text="Data Block Type")
layout.prop(self, "data_block_type", text="", icon="GROUP")
layout.label(text="Data Block")
layout.prop(self, "data_block", text="")
if bpy.data.is_saved:
layout.prop(self, "use_relative_path")
else:
self.use_relative_path = False
layout.label(text="Save the .blend file first ")
layout.label(text="to use relative paths for .ifc.")
def execute(self, context):
if self.data_block_type == "0":
self.report({"ERROR"}, "Select a data block type")
return {"CANCELLED"}
if self.data_block == "":
self.report({"ERROR"}, "Select a data block")
return {"CANCELLED"}
if not os.path.exists(self.filepath):
self.report({"ERROR"}, f"File not found:\n'{self.filepath}'")
return {"CANCELLED"}
db = tool.Blender.append_data_block(self.filepath, self.data_block_type, self.data_block)
if not db["data_block"]:
self.report({"ERROR"}, db["msg"])
return {"CANCELLED"}
bpy.data.materials.remove(db["data_block"])
if not StyleAttributesData.is_loaded:
StyleAttributesData.load()
external_style = StyleAttributesData.data["style_elements"].get("IfcExternallyDefinedSurfaceStyle", None)
if self.use_relative_path:
filepath = os.path.relpath(self.filepath, bpy.path.abspath("//"))
else:
filepath = self.filepath
attributes = {
"Location": filepath,
"Identification": f"{self.data_block_type}/{self.data_block}",
"Name": self.data_block,
}
if not external_style:
core.add_external_style(
tool.Ifc, tool.Style, obj=context.active_object.active_material, attributes=attributes
)
else:
core.update_external_style(tool.Ifc, tool.Style, external_style=external_style, attributes=attributes)
StyleAttributesData.is_loaded = False
return {"FINISHED"}
class EnableEditingExternalStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_external_style"
bl_label = "Enable Editing External Style"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.enable_editing_external_style(tool.Style, obj=context.active_object.active_material)
class DisableEditingExternalStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_external_style"
bl_options = {"REGISTER", "UNDO"}
bl_label = "Disable Editing External Style"
def _execute(self, context):
core.disable_editing_external_style(tool.Style, obj=context.active_object.active_material)
class EditExternalStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_external_style"
bl_label = "Edit External Style"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.edit_external_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
class ActivateExternalStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.activate_external_style"
bl_label = "Activate External Style"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
def _execute(self, context):
material = context.active_object.active_material
external_style = tool.Style.get_style_elements(material)["IfcExternallyDefinedSurfaceStyle"]
data_block_type, data_block = external_style.Identification.split("/")
style_path = Path(tool.Ifc.resolve_uri(external_style.Location))
if style_path.suffix != ".blend":
self.report({"ERROR"}, f"Only Blender external styles are supported")
return {"CANCELLED"}
if not style_path.exists():
self.report({"ERROR"}, f"File not found:\n'{style_path}'")
return {"CANCELLED"}
db = tool.Blender.append_data_block(str(style_path), data_block_type, data_block)
if not db["data_block"]:
self.report({"ERROR"}, db["msg"])
return {"CANCELLED"}
props_to_copy = [
"diffuse_color",
"metallic",
"roughness",
"specular_intensity",
"use_nodes",
]
for prop_name in props_to_copy:
setattr(material, prop_name, getattr(db["data_block"], prop_name))
if material.use_nodes:
tool.Blender.copy_node_graph_to_active_object(context, db["data_block"])
bpy.data.materials.remove(db["data_block"])
class DisableEditingStyles(bpy.types.Operator, tool.Ifc.Operator): class DisableEditingStyles(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_styles" bl_idname = "bim.disable_editing_styles"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -31,6 +31,7 @@ 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):
@@ -45,13 +46,74 @@ class Style(PropertyGroup):
total_elements: IntProperty(name="Total Elements") total_elements: IntProperty(name="Total Elements")
STYLE_TYPES = [
("Shading", "Shading", ""),
("External", "External", ""),
]
def update_shading_styles(self, context):
materials_to_objects = dict()
for obj in bpy.data.objects:
blender_material = obj.active_material
if blender_material and blender_material.BIMMaterialProperties.ifc_style_id != 0:
materials_to_objects[blender_material] = obj
for mat, obj in materials_to_objects.items():
tool.Style.change_current_style_type(context, obj, mat, self.active_style_type)
class BIMStylesProperties(PropertyGroup): class BIMStylesProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing") is_editing: BoolProperty(name="Is Editing")
style_type: EnumProperty(items=get_style_types, name="Style Type") style_type: EnumProperty(items=get_style_types, name="Style Type")
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(
name="Active Style Type",
description="Update current blender material to match style type for all objects in the scene",
items=STYLE_TYPES,
default="Shading",
update=update_shading_styles,
)
def update_shading_style(self, context):
blender_material = context.active_object.active_material
style_elements = tool.Style.get_style_elements(blender_material)
if self.active_style_type == "External":
if "IfcExternallyDefinedSurfaceStyle" in style_elements:
bpy.ops.bim.activate_external_style()
elif self.active_style_type == "Shading":
style_elements = tool.Style.get_style_elements(blender_material)
rendering_style = None
texture_style = None
for surface_style in style_elements.values():
if surface_style.is_a() == "IfcSurfaceStyleShading":
tool.Loader.create_surface_style_shading(blender_material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleRendering"):
rendering_style = surface_style
tool.Loader.create_surface_style_rendering(blender_material, surface_style)
elif surface_style.is_a("IfcSurfaceStyleWithTextures"):
texture_style = surface_style
if rendering_style and texture_style:
tool.Loader.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
class BIMStyleProperties(PropertyGroup): class BIMStyleProperties(PropertyGroup):
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)
is_editing_external_style: BoolProperty(name="Is Editing External Style")
active_style_type: EnumProperty(
name="Active Style Type",
description="Update current blender material to match style type",
items=STYLE_TYPES,
default="Shading",
update=update_shading_style,
)
@@ -82,18 +82,22 @@ class BIM_PT_style(MaterialButtonsPanel, Panel):
def draw(self, context): def draw(self, context):
props = context.active_object.active_material.BIMMaterialProperties props = context.active_object.active_material.BIMMaterialProperties
style_props = context.active_object.active_material.BIMStyleProperties
mat = context.material mat = context.material
row = self.layout.row(align=True) row = self.layout.row(align=True)
if props.ifc_style_id: if not props.ifc_style_id:
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
row = self.layout.row(align=True)
row.prop(mat, "diffuse_color", text="Color")
else:
row.operator("bim.add_style", icon="ADD") row.operator("bim.add_style", icon="ADD")
return
row.prop(style_props, "active_style_type", icon="SHADING_RENDERED", 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
row = self.layout.row(align=True)
row.prop(mat, "diffuse_color", text="Viewport Color" if mat.use_nodes else "Render Color")
class BIM_PT_style_attributes(Panel): class BIM_PT_style_attributes(Panel):
@@ -102,6 +106,7 @@ class BIM_PT_style_attributes(Panel):
bl_space_type = "PROPERTIES" bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW" bl_region_type = "WINDOW"
bl_context = "material" bl_context = "material"
bl_parent_id = "BIM_PT_style"
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@@ -143,6 +148,61 @@ class BIM_PT_style_attributes(Panel):
row.label(text=attribute["value"]) row.label(text=attribute["value"])
class BIM_PT_external_style_attributes(Panel):
bl_label = "IFC External Surface Style"
bl_idname = "BIM_PT_external_style_attributes"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
bl_parent_id = "BIM_PT_style"
@classmethod
def poll(cls, context):
if not IfcStore.get_file():
return False
try:
return bool(context.active_object.active_material.BIMMaterialProperties.ifc_style_id)
except:
return False
def draw(self, context):
if not StyleAttributesData.is_loaded or (
context.active_object.active_material.BIMMaterialProperties.ifc_style_id
!= StyleAttributesData.data["ifc_style_id"]
):
StyleAttributesData.load()
mat = context.active_object.active_material
mprops = mat.BIMMaterialProperties
props = mat.BIMStyleProperties
external_style = StyleAttributesData.data["style_elements"].get("IfcExternallyDefinedSurfaceStyle", None)
if not external_style:
row = self.layout.row(align=True)
row.operator("bim.browse_external_style", text="Add External Style", icon="ADD")
return
row = self.layout.row(align=True)
row.label(text="Parameters:")
if props.is_editing_external_style:
row.operator("bim.edit_external_style", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_external_style", icon="CANCEL", text="")
blenderbim.bim.helper.draw_attributes(props.external_style_attributes, self.layout)
else:
row.operator("bim.browse_external_style", icon="APPEND_BLEND", text="")
row.operator("bim.enable_editing_external_style", icon="GREASEPENCIL", text="")
row = self.layout.row(align=True)
row.label(text="STEP ID")
row.label(text=str(external_style.id()))
for attribute in StyleAttributesData.data["external_style_attributes"]:
row = self.layout.row(align=True)
row.label(text=attribute["name"])
row.label(text=attribute["value"])
class BIM_UL_styles(UIList): class BIM_UL_styles(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item: if item:
+28
View File
@@ -33,6 +33,17 @@ def add_style(ifc, style, obj=None):
return element return element
def add_external_style(ifc, style, obj, attributes):
element = style.get_style(obj)
ifc.run(
"style.add_surface_style", style=element, ifc_class="IfcExternallyDefinedSurfaceStyle", attributes=attributes
)
def update_external_style(ifc, style, external_style, attributes):
ifc.run("style.edit_surface_style", style=external_style, attributes=attributes)
def remove_style(ifc, material, style_tool, style=None): def remove_style(ifc, material, style_tool, style=None):
obj = ifc.get_object(style) obj = ifc.get_object(style)
ifc.unlink(obj=obj, element=style) ifc.unlink(obj=obj, element=style)
@@ -95,16 +106,33 @@ def enable_editing_style(style, obj=None):
style.import_surface_attributes(style.get_style(obj), obj) style.import_surface_attributes(style.get_style(obj), obj)
def enable_editing_external_style(style, obj=None):
external_style = style.get_style_elements(obj)["IfcExternallyDefinedSurfaceStyle"]
style.enable_editing_external_style(obj)
style.import_external_style_attributes(external_style, obj)
def disable_editing_style(style, obj=None): def disable_editing_style(style, obj=None):
style.disable_editing(obj) style.disable_editing(obj)
def disable_editing_external_style(style, obj=None):
style.disable_editing_external_style(obj)
def edit_style(ifc, style, obj=None): def edit_style(ifc, style, obj=None):
attributes = style.export_surface_attributes(obj) attributes = style.export_surface_attributes(obj)
ifc.run("style.edit_presentation_style", style=style.get_style(obj), attributes=attributes) ifc.run("style.edit_presentation_style", style=style.get_style(obj), attributes=attributes)
style.disable_editing(obj) style.disable_editing(obj)
def edit_external_style(ifc, style, obj=None):
attributes = style.export_external_style_attributes(obj)
external_style = style.get_style_elements(obj)["IfcExternallyDefinedSurfaceStyle"]
update_external_style(ifc, style, external_style, attributes)
style.disable_editing_external_style(obj)
def load_styles(style, style_type=None): def load_styles(style, style_type=None):
style.import_presentation_styles(style_type) style.import_presentation_styles(style_type)
style.enable_editing_styles() style.enable_editing_styles()
+49 -4
View File
@@ -108,11 +108,48 @@ class Blender:
return context_override return context_override
@classmethod @classmethod
def update_viewport(cls): def get_shader_editor_context(cls):
# if it stops working in future Blender versions for screen in bpy.data.screens:
# there is an alternative: for area in screen.areas:
# bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) if area.type == "NODE_EDITOR":
for space in area.spaces:
if space.tree_type == "ShaderNodeTree":
context_override = {"area": area, "space": space, "screen": screen}
return context_override
@classmethod
def copy_node_graph_to_active_object(cls, context, material):
"""make sure to override `context.active_object`
to you want change node graph for the object that's not actually active
"""
temp_override = cls.get_shader_editor_context()
old_material = context.active_object.active_material
new_material = material
# remove all nodes from the current material
for n in old_material.node_tree.nodes[:]:
old_material.node_tree.nodes.remove(n)
# change current material and make other window's shader editor is updated
context.active_object.active_material = new_material
temp_override["space"].node_tree = new_material.node_tree
# select all nodes and copy them to clipboard
for node in new_material.node_tree.nodes:
node.select = True
bpy.ops.node.clipboard_copy(temp_override)
# back to original material
context.active_object.active_material = old_material
temp_override["space"].node_tree = old_material.node_tree
bpy.ops.node.clipboard_paste(temp_override, offset=(0, 0))
@classmethod
def update_screen(cls):
bpy.ops.wm.redraw_timer(type="DRAW_WIN_SWAP", iterations=1)
@classmethod
def update_viewport(cls):
tool.Blender.get_viewport_context()["area"].tag_redraw() tool.Blender.get_viewport_context()["area"].tag_redraw()
@classmethod @classmethod
@@ -215,6 +252,14 @@ class Blender:
context.view_layer.objects.active = active_object context.view_layer.objects.active = active_object
active_object.select_set(True) active_object.select_set(True)
@classmethod
def append_data_block(cls, filepath, data_block_type, name, link=False, relative=False):
with bpy.data.libraries.load(filepath, link=link, relative=relative) as (data_from, data_to):
if name not in getattr(data_from, data_block_type):
return {"data_block": None, "msg": f"Data-block {data_block_type}/{name} not found in {filepath}"}
getattr(data_to, data_block_type).append(name)
return {"data_block": getattr(data_to, data_block_type)[0], "msg": ""}
## BMESH UTILS ## ## BMESH UTILS ##
@classmethod @classmethod
def apply_bmesh(cls, mesh, bm, obj=None): def apply_bmesh(cls, mesh, bm, obj=None):
+174
View File
@@ -21,6 +21,8 @@ import bpy
import ifcopenshell.util.element import ifcopenshell.util.element
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.tool as tool import blenderbim.tool as tool
import os
import mathutils
# Progressively we'll refactor loading elements into Blender objects into this # Progressively we'll refactor loading elements into Blender objects into this
@@ -28,6 +30,7 @@ import blenderbim.tool as tool
# partially load and unload objects for huge models, partial model editing, and # partially load and unload objects for huge models, partial model editing, and
# supplementary objects (e.g. drawings, structural analysis models, etc). # supplementary objects (e.g. drawings, structural analysis models, etc).
class Loader(blenderbim.core.tool.Loader): class Loader(blenderbim.core.tool.Loader):
@classmethod @classmethod
def get_mesh_name(cls, geometry): def get_mesh_name(cls, geometry):
@@ -52,3 +55,174 @@ class Loader(blenderbim.core.tool.Loader):
else: else:
# TODO: See #2002 # TODO: See #2002
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.replace(",", "")) mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.replace(",", ""))
@classmethod
def create_surface_style_shading(cls, blender_material, surface_style):
alpha = 1.0
# Transparency was added in IFC4
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
alpha = 1 - surface_style.Transparency
blender_material.diffuse_color = (
surface_style.SurfaceColour.Red,
surface_style.SurfaceColour.Green,
surface_style.SurfaceColour.Blue,
alpha,
)
@classmethod
def restart_material_node_tree(cls, blender_material):
nodes = blender_material.node_tree.nodes
links = blender_material.node_tree.links
for n in nodes[:]:
nodes.remove(n)
output = nodes.new("ShaderNodeOutputMaterial")
bsdf = nodes.new("ShaderNodeBsdfPrincipled")
links.new(bsdf.outputs["BSDF"], output.inputs["Surface"])
@classmethod
def create_surface_style_rendering(cls, blender_material, surface_style):
cls.create_surface_style_shading(blender_material, surface_style)
if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
blender_material.use_nodes = True
cls.restart_material_node_tree(blender_material)
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
if surface_style.DiffuseColour:
if surface_style.DiffuseColour.is_a("IfcColourRgb"):
bsdf.inputs["Base Color"].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
elif surface_style.DiffuseColour.is_a("IfcNormalisedRatioMeasure"):
bsdf.inputs["Base Color"].default_value = (
surface_style.SurfaceColour.Red * surface_style.DiffuseColour.wrappedValue,
surface_style.SurfaceColour.Green * surface_style.DiffuseColour.wrappedValue,
surface_style.SurfaceColour.Blue * surface_style.DiffuseColour.wrappedValue,
1,
)
if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"):
bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue
if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
bsdf.inputs["Roughness"].default_value = surface_style.SpecularHighlight.wrappedValue
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
bsdf.inputs["Alpha"].default_value = 1 - surface_style.Transparency
blender_material.blend_method = "BLEND"
elif surface_style.ReflectanceMethod == "FLAT":
blender_material.use_nodes = True
cls.restart_material_node_tree(blender_material)
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader")
mix.location = bsdf.location
blender_material.node_tree.links.new(mix.outputs[0], output.inputs["Surface"])
blender_material.node_tree.nodes.remove(bsdf)
lightpath = blender_material.node_tree.nodes.new(type="ShaderNodeLightPath")
lightpath.location = mix.location - mathutils.Vector((200, -200))
blender_material.node_tree.links.new(lightpath.outputs[0], mix.inputs[0])
bsdf = blender_material.node_tree.nodes.new(type="ShaderNodeBsdfTransparent")
bsdf.location = mix.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(bsdf.outputs[0], mix.inputs[1])
rgb = blender_material.node_tree.nodes.new(type="ShaderNodeRGB")
rgb.location = mix.location - mathutils.Vector((200, 200))
blender_material.node_tree.links.new(rgb.outputs[0], mix.inputs[2])
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
rgb.outputs[0].default_value = (
surface_style.DiffuseColour.Red,
surface_style.DiffuseColour.Green,
surface_style.DiffuseColour.Blue,
1,
)
@classmethod
def create_surface_style_with_textures(cls, blender_material, rendering_style, texture_style):
for texture in texture_style.Textures:
mode = getattr(texture, "Mode", None)
node = None
if texture.is_a("IfcImageTexture"):
image_url = texture.URLReference
if not os.path.abspath(texture.URLReference) and tool.Ifc.get_path():
image_url = os.path.join(os.path.dirname(tool.Ifc.get_path()), texture.URLReference)
if rendering_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
if mode == "NORMAL":
normalmap = blender_material.node_tree.nodes.new(type="ShaderNodeNormalMap")
normalmap.location = bsdf.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(normalmap.outputs[0], bsdf.inputs["Normal"])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = normalmap.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
image.colorspace_settings.name = "Non-Color"
node.image = image
blender_material.node_tree.links.new(node.outputs[0], normalmap.inputs["Color"])
elif mode == "EMISSIVE":
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
add = blender_material.node_tree.nodes.new(type="ShaderNodeAddShader")
add.location = bsdf.location + mathutils.Vector((200, 0))
blender_material.node_tree.links.new(bsdf.outputs[0], add.inputs[1])
blender_material.node_tree.links.new(add.outputs[0], output.inputs[0])
emission = blender_material.node_tree.nodes.new(type="ShaderNodeEmission")
emission.location = add.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(emission.outputs[0], add.inputs[0])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = emission.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], emission.inputs[0])
elif mode == "METALLICROUGHNESS":
separate = blender_material.node_tree.nodes.new(type="ShaderNodeSeparateRGB")
separate.location = bsdf.location - mathutils.Vector((200, 0))
blender_material.node_tree.links.new(separate.outputs[1], bsdf.inputs["Roughness"])
blender_material.node_tree.links.new(separate.outputs[2], bsdf.inputs["Metallic"])
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = separate.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
image.colorspace_settings.name = "Non-Color"
node.image = image
blender_material.node_tree.links.new(node.outputs[0], separate.inputs[0])
elif mode == "OCCLUSION":
# TODO work out how to implement glTF settings here
# https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html
pass
elif mode == "DIFFUSE":
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = bsdf.location - mathutils.Vector((400, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs["Base Color"])
blender_material.node_tree.links.new(node.outputs[1], bsdf.inputs["Alpha"])
blender_material.blend_method = "BLEND"
elif rendering_style.ReflectanceMethod == "FLAT":
bsdf = blender_material.node_tree.nodes["Mix Shader"]
if mode == "EMISSIVE":
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
node.location = bsdf.location - mathutils.Vector((200, 0))
image = bpy.data.images.load(image_url)
node.image = image
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs[2])
if node and getattr(texture, "IsMappedBy", None):
coordinates = texture.IsMappedBy[0]
coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord")
coord.location = node.location - mathutils.Vector((200, 0))
if coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD":
blender_material.node_tree.links.new(coord.outputs["Generated"], node.inputs["Vector"])
elif coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD-EYE":
blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"])
else:
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
+34 -2
View File
@@ -33,6 +33,10 @@ class Style(blenderbim.core.tool.Style):
def disable_editing(cls, obj): def disable_editing(cls, obj):
obj.BIMStyleProperties.is_editing = False obj.BIMStyleProperties.is_editing = False
@classmethod
def disable_editing_external_style(cls, obj):
obj.BIMStyleProperties.is_editing_external_style = False
@classmethod @classmethod
def disable_editing_styles(cls): def disable_editing_styles(cls):
bpy.context.scene.BIMStylesProperties.is_editing = False bpy.context.scene.BIMStylesProperties.is_editing = False
@@ -41,6 +45,10 @@ class Style(blenderbim.core.tool.Style):
def enable_editing(cls, obj): def enable_editing(cls, obj):
obj.BIMStyleProperties.is_editing = True obj.BIMStyleProperties.is_editing = True
@classmethod
def enable_editing_external_style(cls, obj):
obj.BIMStyleProperties.is_editing_external_style = True
@classmethod @classmethod
def enable_editing_styles(cls): def enable_editing_styles(cls):
bpy.context.scene.BIMStylesProperties.is_editing = True bpy.context.scene.BIMStylesProperties.is_editing = True
@@ -49,6 +57,10 @@ class Style(blenderbim.core.tool.Style):
def export_surface_attributes(cls, obj): def export_surface_attributes(cls, obj):
return blenderbim.bim.helper.export_attributes(obj.BIMStyleProperties.attributes) return blenderbim.bim.helper.export_attributes(obj.BIMStyleProperties.attributes)
@classmethod
def export_external_style_attributes(cls, obj):
return blenderbim.bim.helper.export_attributes(obj.BIMStyleProperties.external_style_attributes)
@classmethod @classmethod
def get_active_style_type(cls): def get_active_style_type(cls):
return bpy.context.scene.BIMStylesProperties.style_type return bpy.context.scene.BIMStylesProperties.style_type
@@ -73,6 +85,14 @@ class Style(blenderbim.core.tool.Style):
except: except:
return return
@classmethod
def get_style_elements(cls, blender_material):
style = tool.Ifc.get().by_id(blender_material.BIMMaterialProperties.ifc_style_id)
style_elements = {}
for style in style.Styles:
style_elements[style.is_a()] = style
return style_elements
@classmethod @classmethod
def get_surface_rendering_attributes(cls, obj): def get_surface_rendering_attributes(cls, obj):
transparency = 1 - obj.diffuse_color[3] transparency = 1 - obj.diffuse_color[3]
@@ -197,8 +217,15 @@ class Style(blenderbim.core.tool.Style):
@classmethod @classmethod
def import_surface_attributes(cls, style, obj): def import_surface_attributes(cls, style, obj):
obj.BIMStyleProperties.attributes.clear() attributes = obj.BIMStyleProperties.attributes
blenderbim.bim.helper.import_attributes2(style, obj.BIMStyleProperties.attributes) attributes.clear()
blenderbim.bim.helper.import_attributes2(style, attributes)
@classmethod
def import_external_style_attributes(cls, style, obj):
attributes = obj.BIMStyleProperties.external_style_attributes
attributes.clear()
blenderbim.bim.helper.import_attributes2(style, attributes)
@classmethod @classmethod
def is_editing_styles(cls): def is_editing_styles(cls):
@@ -214,3 +241,8 @@ class Style(blenderbim.core.tool.Style):
obj = tool.Ifc.get_object(element) obj = tool.Ifc.get_object(element)
if obj: if obj:
obj.select_set(True) obj.select_set(True)
@classmethod
def change_current_style_type(cls, context, obj, blender_material, style_type):
with context.temp_override(active_object=obj):
blender_material.BIMStyleProperties.active_style_type = style_type