Files
IfcOpenShell/src/blenderbim/blenderbim/bim/module/style/operator.py
T
Andrej730 6d152c4fc4 Support for import IfcSurfaceStyleWithTextures
Added UI for adding textures in BBIM shader graph generator

https://imgur.com/a/BI5pOZn
2024-04-15 13:25:50 +02:00

417 lines
15 KiB
Python

# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import blenderbim.bim.handler
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 = "Save Current Shading Style"
bl_description = (
"Save current style values to IfcSurfaceStyleShading.\n\n" + "ALT+CLICK to see saved values details"
)
bl_options = {"REGISTER", "UNDO"}
verbose: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
# verobse print to console on alt+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.alt:
self.verbose = True
return self.execute(context)
def _execute(self, context):
mat = context.active_object.active_material
core.update_style_colours(tool.Ifc, tool.Style, obj=mat, verbose=self.verbose)
if self.verbose:
self.report({"INFO"}, "Check the system console to see saved style properties")
tool.Style.set_surface_style_props(mat)
class UpdateStyleTextures(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.update_style_textures"
bl_label = "Update Style Textures"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
representation = ifcopenshell.util.representation.get_representation(
tool.Ifc.get_entity(context.active_object), "Model", "Body", "MODEL_VIEW"
)
if representation:
core.update_style_textures(
tool.Ifc, tool.Style, obj=context.active_object.active_material, representation=representation
)
class RemoveStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_style"
bl_label = "Remove Style"
bl_options = {"REGISTER", "UNDO"}
style: bpy.props.IntProperty()
def _execute(self, context):
core.remove_style(tool.Ifc, tool.Material, tool.Style, style=tool.Ifc.get().by_id(self.style))
class AddStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_style"
bl_label = "Add Style"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.add_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
class UnlinkStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unlink_style"
bl_label = "Unlink Style"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.unlink_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
class EnableEditingStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.enable_editing_style"
bl_label = "Enable Editing Style"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.enable_editing_style(tool.Style, obj=context.active_object.active_material)
class DisableEditingStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_style"
bl_options = {"REGISTER", "UNDO"}
bl_label = "Disable Editing Style"
def _execute(self, context):
core.disable_editing_style(tool.Style, obj=context.active_object.active_material)
class EditStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_style"
bl_label = "Edit Style"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.edit_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
class UpdateCurrentStyle(bpy.types.Operator):
bl_idname = "bim.update_current_style"
bl_label = "Update Current Style"
bl_description = (
"Update style for all selected objects according to current style type\n(Shading/External).\n\n"
+ "SHIFT+CLICK to update ALL styles in the .ifc file to current style type"
)
bl_options = {"REGISTER", "UNDO"}
update_all: bpy.props.BoolProperty(name="Update All", default=False, options={"SKIP_SAVE"})
@classmethod
def poll(cls, context):
poll = (
context.active_object is not None
and context.active_object.active_material is not None
and context.active_object.active_material.BIMMaterialProperties.ifc_style_id != 0
)
if not poll:
cls.poll_message_set(
"Object is not selected or material is not assigned or material doesn't have IFC Style"
)
return poll
def invoke(self, context, event):
# updating all styles on shift+click
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
if event.type == "LEFTMOUSE" and event.shift:
self.update_all = True
return self.execute(context)
def execute(self, context):
current_style_type = context.active_object.active_material.BIMStyleProperties.active_style_type
if self.update_all:
context.scene.BIMStylesProperties.active_style_type = current_style_type
return {"FINISHED"}
materials = []
for obj in context.selected_objects:
mat = obj.active_material
if mat and mat.BIMMaterialProperties.ifc_style_id != 0:
mat.BIMStyleProperties.active_style_type = current_style_type
return {"FINISHED"}
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"}
material_name: bpy.props.StringProperty(name="Material Name", default="")
def _execute(self, context):
if not self.material_name:
material = context.active_object.active_material
else:
material = bpy.data.materials[self.material_name]
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(material, 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"}
bl_label = "Disable Editing Styles"
def _execute(self, context):
core.disable_editing_styles(tool.Style)
class LoadStyles(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.load_styles"
bl_label = "Load Styles"
bl_options = {"REGISTER", "UNDO"}
style_type: bpy.props.StringProperty()
def _execute(self, context):
core.load_styles(tool.Style, style_type=self.style_type)
class SelectByStyle(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.select_by_style"
bl_label = "Select By Material"
bl_options = {"REGISTER", "UNDO"}
style: bpy.props.IntProperty()
def _execute(self, context):
core.select_by_style(tool.Style, tool.Spatial, style=tool.Ifc.get().by_id(self.style))
class ClearTextureMapPath(bpy.types.Operator):
bl_idname = "bim.clear_texture_map_path"
bl_label = "Clear Texture Map Path"
bl_options = {"REGISTER", "UNDO"}
texture_map_prop: bpy.props.StringProperty(default="")
@classmethod
def poll(cls, context):
poll = getattr(context, "material", None)
if not poll:
cls.poll_message_set("Select a material")
return poll
def execute(self, context):
if not self.texture_map_prop:
self.report({"ERROR"}, "Provide a texture map")
return {"CANCELLED"}
props = context.material.BIMStyleProperties
setattr(props, self.texture_map_prop, "")
return {"FINISHED"}