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:
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"):
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"):
texture_style = surface_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)
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):
for ifc_definition_id, obj in self.added_data.items():
if isinstance(obj, bpy.types.Object):
@@ -30,7 +30,8 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
def draw(self, context):
layout = self.layout
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.prop(props, "override_colour", text="")
row.operator("bim.set_override_colour")
@@ -21,10 +21,15 @@ from . import ui, prop, operator
classes = (
operator.AddStyle,
operator.DisableEditingStyle,
operator.DisableEditingStyles,
operator.EditStyle,
operator.EnableEditingStyle,
operator.DisableEditingStyle,
operator.EditStyle,
operator.EnableEditingExternalStyle,
operator.DisableEditingExternalStyle,
operator.EditExternalStyle,
operator.DisableEditingStyles,
operator.BrowseExternalStyle,
operator.ActivateExternalStyle,
operator.LoadStyles,
operator.RemoveStyle,
operator.SelectByStyle,
@@ -37,6 +42,7 @@ classes = (
ui.BIM_PT_styles,
ui.BIM_PT_style,
ui.BIM_PT_style_attributes,
ui.BIM_PT_external_style_attributes,
ui.BIM_UL_styles,
)
@@ -56,7 +56,12 @@ class StyleAttributesData:
@classmethod
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
@classmethod
@@ -72,3 +77,19 @@ class StyleAttributesData:
continue
results.append({"name": name, "value": str(value)})
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 ifcopenshell.util.representation
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):
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"}
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)
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):
bl_idname = "bim.disable_editing_styles"
bl_options = {"REGISTER", "UNDO"}
@@ -31,6 +31,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
import blenderbim.tool as tool
def get_style_types(self, context):
@@ -45,13 +46,74 @@ class Style(PropertyGroup):
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):
is_editing: BoolProperty(name="Is Editing")
style_type: EnumProperty(items=get_style_types, name="Style Type")
styles: CollectionProperty(name="Styles", type=Style)
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):
attributes: CollectionProperty(name="Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing")
external_style_attributes: CollectionProperty(name="External Style Attributes", type=Attribute)
is_editing_external_style: BoolProperty(name="Is Editing External Style")
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):
props = context.active_object.active_material.BIMMaterialProperties
style_props = context.active_object.active_material.BIMStyleProperties
mat = context.material
row = self.layout.row(align=True)
if 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:
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 = 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):
@@ -102,6 +106,7 @@ class BIM_PT_style_attributes(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
bl_parent_id = "BIM_PT_style"
@classmethod
def poll(cls, context):
@@ -143,6 +148,61 @@ class BIM_PT_style_attributes(Panel):
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):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
+28
View File
@@ -33,6 +33,17 @@ def add_style(ifc, style, obj=None):
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):
obj = ifc.get_object(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)
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):
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):
attributes = style.export_surface_attributes(obj)
ifc.run("style.edit_presentation_style", style=style.get_style(obj), attributes=attributes)
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):
style.import_presentation_styles(style_type)
style.enable_editing_styles()
+49 -4
View File
@@ -108,11 +108,48 @@ class Blender:
return context_override
@classmethod
def update_viewport(cls):
# if it stops working in future Blender versions
# there is an alternative:
# bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
def get_shader_editor_context(cls):
for screen in bpy.data.screens:
for area in screen.areas:
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()
@classmethod
@@ -215,6 +252,14 @@ class Blender:
context.view_layer.objects.active = active_object
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 ##
@classmethod
def apply_bmesh(cls, mesh, bm, obj=None):
+174
View File
@@ -21,6 +21,8 @@ import bpy
import ifcopenshell.util.element
import blenderbim.core.tool
import blenderbim.tool as tool
import os
import mathutils
# 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
# supplementary objects (e.g. drawings, structural analysis models, etc).
class Loader(blenderbim.core.tool.Loader):
@classmethod
def get_mesh_name(cls, geometry):
@@ -52,3 +55,174 @@ class Loader(blenderbim.core.tool.Loader):
else:
# TODO: See #2002
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):
obj.BIMStyleProperties.is_editing = False
@classmethod
def disable_editing_external_style(cls, obj):
obj.BIMStyleProperties.is_editing_external_style = False
@classmethod
def disable_editing_styles(cls):
bpy.context.scene.BIMStylesProperties.is_editing = False
@@ -41,6 +45,10 @@ class Style(blenderbim.core.tool.Style):
def enable_editing(cls, obj):
obj.BIMStyleProperties.is_editing = True
@classmethod
def enable_editing_external_style(cls, obj):
obj.BIMStyleProperties.is_editing_external_style = True
@classmethod
def enable_editing_styles(cls):
bpy.context.scene.BIMStylesProperties.is_editing = True
@@ -49,6 +57,10 @@ class Style(blenderbim.core.tool.Style):
def export_surface_attributes(cls, obj):
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
def get_active_style_type(cls):
return bpy.context.scene.BIMStylesProperties.style_type
@@ -73,6 +85,14 @@ class Style(blenderbim.core.tool.Style):
except:
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
def get_surface_rendering_attributes(cls, obj):
transparency = 1 - obj.diffuse_color[3]
@@ -197,8 +217,15 @@ class Style(blenderbim.core.tool.Style):
@classmethod
def import_surface_attributes(cls, style, obj):
obj.BIMStyleProperties.attributes.clear()
blenderbim.bim.helper.import_attributes2(style, obj.BIMStyleProperties.attributes)
attributes = 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
def is_editing_styles(cls):
@@ -214,3 +241,8 @@ class Style(blenderbim.core.tool.Style):
obj = tool.Ifc.get_object(element)
if obj:
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