mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-05 23:41:44 +00:00
Merge pull request #8342 from falken10vdl/style-flat-pretty-toggle
Style flat pretty toggle
This commit is contained in:
@@ -418,9 +418,10 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None
|
|||||||
|
|
||||||
|
|
||||||
def viewport_shading_changed_callback(area: bpy.types.Area) -> None:
|
def viewport_shading_changed_callback(area: bpy.types.Area) -> None:
|
||||||
shading = area.spaces.active.shading.type
|
shading_type = area.spaces.active.shading.type
|
||||||
if shading == "RENDERED":
|
tool.Style.restore_material_style_types(shading_type)
|
||||||
tool.Style.get_style_props().active_style_type = "External"
|
if shading_type == "SOLID":
|
||||||
|
area.spaces.active.shading.color_type = "MATERIAL"
|
||||||
|
|
||||||
|
|
||||||
def subscribe_to_viewport_shading_changes():
|
def subscribe_to_viewport_shading_changes():
|
||||||
|
|||||||
@@ -133,10 +133,6 @@ class MaterialCreator:
|
|||||||
if shape_has_openings and coords.is_a("IfcIndexedTextureMap"):
|
if shape_has_openings and coords.is_a("IfcIndexedTextureMap"):
|
||||||
continue
|
continue
|
||||||
tool.Loader.load_indexed_map(coords, self.mesh)
|
tool.Loader.load_indexed_map(coords, self.mesh)
|
||||||
elif tool.Style.get_texture_style(material):
|
|
||||||
# No explicit coordinate mapping (e.g. IFC2X3 has no IsMappedBy,
|
|
||||||
# and IFC4 COORD uses generated UVs). Bake XY→UV as fallback.
|
|
||||||
tool.Loader.load_generated_uv_map(self.mesh)
|
|
||||||
|
|
||||||
def assign_material_slots_to_faces(self) -> None:
|
def assign_material_slots_to_faces(self) -> None:
|
||||||
if not self.mesh["ios_materials"]:
|
if not self.mesh["ios_materials"]:
|
||||||
|
|||||||
@@ -1299,6 +1299,10 @@ class LoadProjectElements(bpy.types.Operator):
|
|||||||
tool.Project.set_default_modeling_dimensions()
|
tool.Project.set_default_modeling_dimensions()
|
||||||
tool.Root.reload_grid_decorator()
|
tool.Root.reload_grid_decorator()
|
||||||
bonsai.bim.handler.refresh_ui_data()
|
bonsai.bim.handler.refresh_ui_data()
|
||||||
|
for screen in bpy.data.screens:
|
||||||
|
for area in screen.areas:
|
||||||
|
if area.type == "VIEW_3D":
|
||||||
|
bonsai.bim.handler.viewport_shading_changed_callback(area)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def get_decomposition_elements(self) -> set[ifcopenshell.entity_instance]:
|
def get_decomposition_elements(self) -> set[ifcopenshell.entity_instance]:
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ classes = (
|
|||||||
operator.SelectByStyle,
|
operator.SelectByStyle,
|
||||||
operator.SelectStyleInStylesUI,
|
operator.SelectStyleInStylesUI,
|
||||||
operator.SetAssetMaterialToExternalStyle,
|
operator.SetAssetMaterialToExternalStyle,
|
||||||
|
operator.SuggestShadeFromExternalStyle,
|
||||||
operator.UnlinkStyle,
|
operator.UnlinkStyle,
|
||||||
|
operator.TogglePreferIfcShading,
|
||||||
operator.UpdateCurrentStyle,
|
operator.UpdateCurrentStyle,
|
||||||
operator.UpdateStyleColours,
|
operator.UpdateStyleColours,
|
||||||
operator.UpdateStyleTextures,
|
operator.UpdateStyleTextures,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import colorsys
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Union
|
from typing import Any, Union
|
||||||
@@ -238,13 +239,15 @@ class UpdateCurrentStyle(bpy.types.Operator):
|
|||||||
if not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve)):
|
if not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve)):
|
||||||
continue
|
continue
|
||||||
for mat in obj.data.materials:
|
for mat in obj.data.materials:
|
||||||
if (
|
if not mat:
|
||||||
mat
|
continue
|
||||||
and mat not in updated_materials
|
msprops_ = tool.Style.get_material_style_props(mat)
|
||||||
and (msprops_ := tool.Style.get_material_style_props(mat)).ifc_definition_id != 0
|
if msprops_.ifc_definition_id == 0:
|
||||||
):
|
continue
|
||||||
msprops_.active_style_type = current_style_type
|
if mat in updated_materials:
|
||||||
updated_materials.add(mat)
|
continue
|
||||||
|
msprops_.active_style_type = current_style_type
|
||||||
|
updated_materials.add(mat)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -457,10 +460,14 @@ class ActivateExternalStyle(bpy.types.Operator):
|
|||||||
self.report({"ERROR"}, f"Error loading external style for \"{material.name}\" - {db['msg']}")
|
self.report({"ERROR"}, f"Error loading external style for \"{material.name}\" - {db['msg']}")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
self.copy_material_attributes(db["data_block"], material)
|
ext_mat = db["data_block"]
|
||||||
|
self.copy_material_attributes(ext_mat, material)
|
||||||
if tool.Style.get_use_nodes(material):
|
if tool.Style.get_use_nodes(material):
|
||||||
tool.Blender.copy_node_graph(material, db["data_block"])
|
if material.get("bim_dual_branch"):
|
||||||
bpy.data.materials.remove(db["data_block"])
|
tool.Style.update_external_branch(material, ext_mat)
|
||||||
|
else:
|
||||||
|
tool.Style.setup_dual_branch(material, ext_mat)
|
||||||
|
bpy.data.materials.remove(ext_mat)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def copy_material_attributes(self, source, target):
|
def copy_material_attributes(self, source, target):
|
||||||
@@ -503,6 +510,218 @@ class ActivateExternalStyle(bpy.types.Operator):
|
|||||||
set_prop(prop_name)
|
set_prop(prop_name)
|
||||||
|
|
||||||
|
|
||||||
|
class TogglePreferIfcShading(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.toggle_prefer_ifc_shading"
|
||||||
|
bl_label = "Toggle Flat/Pretty"
|
||||||
|
bl_description = (
|
||||||
|
"Toggle between Flat (IFC-native shading) and Pretty (external .blend style) for ALL styles.\n\n"
|
||||||
|
"SHIFT+CLICK to apply to this style only"
|
||||||
|
)
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
material_name: bpy.props.StringProperty(name="Material Name", default="", options={"SKIP_SAVE"})
|
||||||
|
single_only: bpy.props.BoolProperty(name="Single Only", default=False, options={"SKIP_SAVE"})
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
if event.shift:
|
||||||
|
self.single_only = True
|
||||||
|
return self.execute(context)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
wm = context.window_manager
|
||||||
|
space = tool.Blender.get_view3d_space()
|
||||||
|
is_solid = space and space.shading.type == "SOLID"
|
||||||
|
|
||||||
|
if is_solid:
|
||||||
|
if space.shading.color_type == "TEXTURE":
|
||||||
|
space.shading.color_type = "MATERIAL"
|
||||||
|
else:
|
||||||
|
meshes_needing_uv = []
|
||||||
|
for obj in bpy.context.scene.objects:
|
||||||
|
if not isinstance(obj.data, bpy.types.Mesh):
|
||||||
|
continue
|
||||||
|
for slot in obj.material_slots:
|
||||||
|
mat = slot.material
|
||||||
|
if not mat or not tool.Blender.get_ifc_definition_id(mat):
|
||||||
|
continue
|
||||||
|
style_elements = tool.Style.get_style_elements(mat)
|
||||||
|
if style_elements.get("IfcSurfaceStyleWithTextures") and not obj.data.uv_layers:
|
||||||
|
meshes_needing_uv.append(obj.data)
|
||||||
|
break
|
||||||
|
wm.progress_begin(0, max(len(meshes_needing_uv), 1))
|
||||||
|
try:
|
||||||
|
for i, mesh in enumerate(meshes_needing_uv):
|
||||||
|
tool.Loader.load_generated_uv_map(mesh)
|
||||||
|
wm.progress_update(i)
|
||||||
|
finally:
|
||||||
|
wm.progress_end()
|
||||||
|
space.shading.color_type = "TEXTURE"
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
if self.single_only:
|
||||||
|
mat = bpy.data.materials.get(self.material_name)
|
||||||
|
if not mat:
|
||||||
|
return {"CANCELLED"}
|
||||||
|
msprops = tool.Style.get_material_style_props(mat)
|
||||||
|
msprops.prefer_ifc_shading = not msprops.prefer_ifc_shading
|
||||||
|
else:
|
||||||
|
# Default: apply to all IFC materials
|
||||||
|
source_mat = bpy.data.materials.get(self.material_name)
|
||||||
|
new_value = not tool.Style.get_material_style_props(source_mat).prefer_ifc_shading if source_mat else True
|
||||||
|
ifc_mats = [m for m in bpy.data.materials if tool.Blender.get_ifc_definition_id(m)]
|
||||||
|
for mat in ifc_mats:
|
||||||
|
tool.Style.get_material_style_props(mat).prefer_ifc_shading = new_value
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class SuggestShadeFromExternalStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
|
bl_idname = "bim.suggest_shade_from_external_style"
|
||||||
|
bl_label = "Suggest Shade from External Style"
|
||||||
|
bl_description = (
|
||||||
|
"Generate a Shade style (Surface Colour + Transparency) from the external .blend style.\n\n"
|
||||||
|
"ALT+CLICK to apply to all styles with an external .blend style"
|
||||||
|
)
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
material_name: bpy.props.StringProperty(name="Material Name", default="", options={"SKIP_SAVE"})
|
||||||
|
all_styles: bpy.props.BoolProperty(name="All Styles", default=False, options={"SKIP_SAVE"})
|
||||||
|
value_offset: bpy.props.FloatProperty(
|
||||||
|
name="Value",
|
||||||
|
description="Offset added to the colour's value (-1 = fully dark, 0 = unchanged, +1 = fully light)",
|
||||||
|
default=0.0,
|
||||||
|
min=-1.0,
|
||||||
|
max=1.0,
|
||||||
|
step=1,
|
||||||
|
precision=2,
|
||||||
|
options={"SKIP_SAVE"},
|
||||||
|
)
|
||||||
|
saturation_factor: bpy.props.FloatProperty(
|
||||||
|
name="Saturation",
|
||||||
|
description="Scale applied to the colour's saturation (0 = greyscale, 1 = unchanged, >1 = more saturated)",
|
||||||
|
default=1.0,
|
||||||
|
min=0.0,
|
||||||
|
max=2.0,
|
||||||
|
step=1,
|
||||||
|
precision=2,
|
||||||
|
options={"SKIP_SAVE"},
|
||||||
|
)
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
if event.alt:
|
||||||
|
self.all_styles = True
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, "value_offset", slider=True)
|
||||||
|
layout.prop(self, "saturation_factor", slider=True)
|
||||||
|
if self.all_styles:
|
||||||
|
layout.label(text="Will apply to all external styles", icon="INFO")
|
||||||
|
|
||||||
|
def _execute(self, context):
|
||||||
|
if self.all_styles:
|
||||||
|
candidates = [
|
||||||
|
(mat, tool.Style.get_style_elements(mat))
|
||||||
|
for mat in bpy.data.materials
|
||||||
|
if tool.Blender.get_ifc_definition_id(mat)
|
||||||
|
]
|
||||||
|
candidates = [(mat, se) for mat, se in candidates if tool.Style.has_blender_external_style(se)]
|
||||||
|
wm = context.window_manager
|
||||||
|
wm.progress_begin(0, max(len(candidates), 1))
|
||||||
|
count = 0
|
||||||
|
color_cache: dict[tuple[str, str, str], tuple | None] = {}
|
||||||
|
try:
|
||||||
|
for i, (mat, style_elements) in enumerate(candidates):
|
||||||
|
wm.progress_update(i)
|
||||||
|
if self._apply_to_material(
|
||||||
|
mat, style_elements, self.value_offset, self.saturation_factor, color_cache
|
||||||
|
):
|
||||||
|
count += 1
|
||||||
|
finally:
|
||||||
|
wm.progress_end()
|
||||||
|
self.report({"INFO"}, f"Shade style generated for {count} style(s).")
|
||||||
|
else:
|
||||||
|
mat = bpy.data.materials.get(self.material_name)
|
||||||
|
if not mat:
|
||||||
|
return {"CANCELLED"}
|
||||||
|
style_elements = tool.Style.get_style_elements(mat)
|
||||||
|
if not tool.Style.has_blender_external_style(style_elements):
|
||||||
|
self.report({"ERROR"}, "No external .blend style assigned. Please assign an external style first.")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
self._apply_to_material(mat, style_elements, self.value_offset, self.saturation_factor)
|
||||||
|
props = tool.Style.get_style_props()
|
||||||
|
if props.is_editing:
|
||||||
|
core.load_styles(tool.Style, style_type=props.style_type)
|
||||||
|
|
||||||
|
def _apply_to_material(
|
||||||
|
self,
|
||||||
|
material: bpy.types.Material,
|
||||||
|
style_elements: dict,
|
||||||
|
value_offset: float = 0.0,
|
||||||
|
saturation_factor: float = 1.0,
|
||||||
|
color_cache: "dict[tuple[str, str, str], tuple | None] | None" = None,
|
||||||
|
) -> bool:
|
||||||
|
external_style = style_elements["IfcExternallyDefinedSurfaceStyle"]
|
||||||
|
style_path = Path(tool.Ifc.resolve_uri(external_style.Location))
|
||||||
|
data_block_type, data_block = external_style.Identification.split("/")
|
||||||
|
|
||||||
|
cache_key = (str(style_path), data_block_type, data_block)
|
||||||
|
if color_cache is not None and cache_key in color_cache:
|
||||||
|
cached = color_cache[cache_key]
|
||||||
|
if cached is None:
|
||||||
|
return False # previously failed for this path
|
||||||
|
surface_colour, transparency = cached
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
db = tool.Blender.append_data_block(str(style_path), data_block_type, data_block)
|
||||||
|
except OSError as e:
|
||||||
|
self.report({"WARNING"}, f'Could not open blend file for "{material.name}": {e}')
|
||||||
|
if color_cache is not None:
|
||||||
|
color_cache[cache_key] = None
|
||||||
|
return False
|
||||||
|
if not db["data_block"]:
|
||||||
|
self.report({"WARNING"}, f'Could not load external style for "{material.name}": {db["msg"]}')
|
||||||
|
if color_cache is not None:
|
||||||
|
color_cache[cache_key] = None
|
||||||
|
return False
|
||||||
|
|
||||||
|
ext_mat = db["data_block"]
|
||||||
|
surface_colour, transparency = tool.Style.get_representative_material_color(ext_mat)
|
||||||
|
bpy.data.materials.remove(ext_mat)
|
||||||
|
if color_cache is not None:
|
||||||
|
color_cache[cache_key] = (surface_colour, transparency)
|
||||||
|
|
||||||
|
if value_offset != 0.0 or saturation_factor != 1.0:
|
||||||
|
h, s, v = colorsys.rgb_to_hsv(*surface_colour)
|
||||||
|
v = max(0.0, min(1.0, v + value_offset))
|
||||||
|
s = max(0.0, min(1.0, s * saturation_factor))
|
||||||
|
surface_colour = colorsys.hsv_to_rgb(h, s, v)
|
||||||
|
|
||||||
|
ifc_style = tool.Ifc.get_entity(material)
|
||||||
|
attributes: dict = {
|
||||||
|
"SurfaceColour": {
|
||||||
|
"Name": None,
|
||||||
|
"Red": surface_colour[0],
|
||||||
|
"Green": surface_colour[1],
|
||||||
|
"Blue": surface_colour[2],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if tool.Ifc.get_schema() != "IFC2X3":
|
||||||
|
attributes["Transparency"] = transparency
|
||||||
|
|
||||||
|
shading_style = style_elements.get("IfcSurfaceStyleShading")
|
||||||
|
if shading_style:
|
||||||
|
tool.Ifc.run("style.edit_surface_style", style=shading_style, attributes=attributes)
|
||||||
|
else:
|
||||||
|
tool.Ifc.run(
|
||||||
|
"style.add_surface_style",
|
||||||
|
style=ifc_style,
|
||||||
|
ifc_class="IfcSurfaceStyleShading",
|
||||||
|
attributes=attributes,
|
||||||
|
)
|
||||||
|
material.diffuse_color = (*surface_colour, 1.0 - transparency)
|
||||||
|
tool.Style.sync_flat_branch_shading(material, surface_colour, transparency)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class DisableEditingStyles(bpy.types.Operator):
|
class DisableEditingStyles(bpy.types.Operator):
|
||||||
bl_idname = "bim.disable_editing_styles"
|
bl_idname = "bim.disable_editing_styles"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|||||||
@@ -372,6 +372,16 @@ def update_shading_style(self: "BIMStyleProperties", context: bpy.types.Context)
|
|||||||
tool.Style.switch_shading(blender_material, self.active_style_type)
|
tool.Style.switch_shading(blender_material, self.active_style_type)
|
||||||
|
|
||||||
|
|
||||||
|
def update_prefer_ifc_shading(self: "BIMStyleProperties", context: bpy.types.Context) -> None:
|
||||||
|
style_elements = tool.Style.get_style_elements(self.id_data)
|
||||||
|
has_external = tool.Style.has_blender_external_style(style_elements)
|
||||||
|
if self.prefer_ifc_shading or not has_external:
|
||||||
|
self.active_style_type = "Shading"
|
||||||
|
else:
|
||||||
|
self.active_style_type = "External"
|
||||||
|
self.id_data.update_tag()
|
||||||
|
|
||||||
|
|
||||||
class BIMStyleProperties(PropertyGroup):
|
class BIMStyleProperties(PropertyGroup):
|
||||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||||
active_style_type: EnumProperty(
|
active_style_type: EnumProperty(
|
||||||
@@ -381,9 +391,19 @@ class BIMStyleProperties(PropertyGroup):
|
|||||||
default="Shading",
|
default="Shading",
|
||||||
update=update_shading_style,
|
update=update_shading_style,
|
||||||
)
|
)
|
||||||
|
prefer_ifc_shading: BoolProperty(
|
||||||
|
name="Flat / Pretty",
|
||||||
|
description=(
|
||||||
|
"Toggle between Flat (IFC-native shading) and Pretty (external .blend style). "
|
||||||
|
"When set to Flat, viewport switches to Material Preview or Rendered will not activate the external style."
|
||||||
|
),
|
||||||
|
default=False,
|
||||||
|
update=update_prefer_ifc_shading,
|
||||||
|
)
|
||||||
is_renaming: BoolProperty(description="Used to prevent triggering handler callback.", default=False)
|
is_renaming: BoolProperty(description="Used to prevent triggering handler callback.", default=False)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
ifc_definition_id: int
|
ifc_definition_id: int
|
||||||
active_style_type: tool.Style.StyleType
|
active_style_type: tool.Style.StyleType
|
||||||
|
prefer_ifc_shading: bool
|
||||||
is_renaming: bool
|
is_renaming: bool
|
||||||
|
|||||||
@@ -110,6 +110,11 @@ class BIM_PT_styles(Panel):
|
|||||||
op = row.operator("bim.update_current_style", icon="FILE_REFRESH", text="")
|
op = row.operator("bim.update_current_style", icon="FILE_REFRESH", text="")
|
||||||
op.style_id = style.ifc_definition_id
|
op.style_id = style.ifc_definition_id
|
||||||
|
|
||||||
|
if active_style and self.props.style_type == "IfcSurfaceStyle":
|
||||||
|
if material := style.blender_material:
|
||||||
|
msprops = tool.Style.get_material_style_props(material)
|
||||||
|
self.draw_style_status_row(material, msprops)
|
||||||
|
|
||||||
if self.props.style_type == "IfcSurfaceStyle":
|
if self.props.style_type == "IfcSurfaceStyle":
|
||||||
self.layout.label(text="Surface Style Element:")
|
self.layout.label(text="Surface Style Element:")
|
||||||
col = self.layout.column(align=True)
|
col = self.layout.column(align=True)
|
||||||
@@ -161,6 +166,66 @@ class BIM_PT_styles(Panel):
|
|||||||
edit_label = "Save Lighting Style"
|
edit_label = "Save Lighting Style"
|
||||||
self.draw_edit_ui(edit_label)
|
self.draw_edit_ui(edit_label)
|
||||||
|
|
||||||
|
def draw_style_status_row(self, material: bpy.types.Material, msprops) -> None:
|
||||||
|
space = tool.Blender.get_view3d_space()
|
||||||
|
box = self.layout.box()
|
||||||
|
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
if space:
|
||||||
|
shading_type = space.shading.type
|
||||||
|
shading_labels = {
|
||||||
|
"SOLID": "Solid",
|
||||||
|
"MATERIAL": "Material Preview",
|
||||||
|
"RENDERED": "Rendered",
|
||||||
|
"WIREFRAME": "Wireframe",
|
||||||
|
}
|
||||||
|
parts.append(f"Viewport: {shading_labels.get(shading_type, shading_type)}")
|
||||||
|
else:
|
||||||
|
parts.append("No 3D viewport")
|
||||||
|
shading_type = None
|
||||||
|
|
||||||
|
if obj:
|
||||||
|
obj_has_uv = isinstance(obj.data, bpy.types.Mesh) and bool(obj.data.uv_layers)
|
||||||
|
uv_label = "UV \u2713" if obj_has_uv else "UV \u2717"
|
||||||
|
parts.append(f"Selected Object: {obj.name} {uv_label}")
|
||||||
|
else:
|
||||||
|
parts.append("Selected Object: None")
|
||||||
|
|
||||||
|
if shading_type == "SOLID":
|
||||||
|
is_flat = space.shading.color_type != "TEXTURE"
|
||||||
|
mode_label = "Flat"
|
||||||
|
dep_label = "Shade"
|
||||||
|
if not is_flat:
|
||||||
|
mode_label = "Pretty"
|
||||||
|
dep_label = "Texture \u2192 Shade"
|
||||||
|
elif shading_type in ("MATERIAL", "RENDERED"):
|
||||||
|
is_flat = msprops.prefer_ifc_shading
|
||||||
|
mode_label = "Flat"
|
||||||
|
dep_label = "Render+Texture \u2192 Render \u2192 Shade"
|
||||||
|
if not is_flat:
|
||||||
|
mode_label = "Pretty"
|
||||||
|
dep_label = "External \u2192 Render+Texture \u2192 Render \u2192 Shade"
|
||||||
|
else:
|
||||||
|
is_flat = False
|
||||||
|
mode_label = ""
|
||||||
|
dep_label = ""
|
||||||
|
|
||||||
|
row1 = box.row(align=True)
|
||||||
|
row1.label(text=" | ".join(parts))
|
||||||
|
|
||||||
|
if mode_label:
|
||||||
|
row2 = box.row(align=True)
|
||||||
|
row2.label(text=f"Current Mode: {mode_label} \u2014 {dep_label}")
|
||||||
|
|
||||||
|
row3 = box.row(align=True)
|
||||||
|
row3.alignment = "RIGHT"
|
||||||
|
op = row3.operator("bim.suggest_shade_from_external_style", text="", icon="BRUSHES_ALL")
|
||||||
|
op.material_name = material.name
|
||||||
|
op = row3.operator("bim.toggle_prefer_ifc_shading", text="", icon="UV_SYNC_SELECT")
|
||||||
|
op.material_name = material.name
|
||||||
|
|
||||||
def draw_surface_style_shading(self):
|
def draw_surface_style_shading(self):
|
||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.prop(self.props, "surface_colour")
|
row.prop(self.props, "surface_colour")
|
||||||
|
|||||||
@@ -803,6 +803,40 @@ class Blender(bonsai.core.tool.Blender):
|
|||||||
# restore shader editor settings
|
# restore shader editor settings
|
||||||
shader_editor.pin = previous_pin_setting
|
shader_editor.pin = previous_pin_setting
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def copy_node_graph_additive(
|
||||||
|
cls, material_to: bpy.types.Material, material_from: bpy.types.Material
|
||||||
|
) -> bpy.types.ShaderNodeOutputMaterial | None:
|
||||||
|
"""Paste nodes from material_from alongside the existing nodes in material_to.
|
||||||
|
|
||||||
|
Unlike copy_node_graph this does NOT clear the existing node tree first.
|
||||||
|
Returns the OUTPUT_MATERIAL node that was added from material_from, or None.
|
||||||
|
"""
|
||||||
|
temp_override = cls.get_shader_editor_context()
|
||||||
|
shader_editor = temp_override["space"]
|
||||||
|
|
||||||
|
before_names = {n.name for n in material_to.node_tree.nodes}
|
||||||
|
|
||||||
|
previous_pin_setting = shader_editor.pin
|
||||||
|
shader_editor.pin = True
|
||||||
|
shader_editor.node_tree = material_from.node_tree
|
||||||
|
|
||||||
|
for node in material_from.node_tree.nodes:
|
||||||
|
node.select = True
|
||||||
|
with bpy.context.temp_override(**temp_override):
|
||||||
|
bpy.ops.node.clipboard_copy()
|
||||||
|
|
||||||
|
shader_editor.node_tree = material_to.node_tree
|
||||||
|
with bpy.context.temp_override(**temp_override):
|
||||||
|
bpy.ops.node.clipboard_paste(offset=(0, 0))
|
||||||
|
|
||||||
|
shader_editor.pin = previous_pin_setting
|
||||||
|
|
||||||
|
for node in material_to.node_tree.nodes:
|
||||||
|
if node.name not in before_names and node.type == "OUTPUT_MATERIAL":
|
||||||
|
return node
|
||||||
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_material_node(
|
def get_material_node(
|
||||||
cls, blender_material: bpy.types.Material, node_type: str, kwargs: Optional[dict] = {}
|
cls, blender_material: bpy.types.Material, node_type: str, kwargs: Optional[dict] = {}
|
||||||
|
|||||||
@@ -597,6 +597,146 @@ class Style(bonsai.core.tool.Style):
|
|||||||
external_style = style_elements.get("IfcExternallyDefinedSurfaceStyle", None)
|
external_style = style_elements.get("IfcExternallyDefinedSurfaceStyle", None)
|
||||||
return bool(external_style and external_style.Location and external_style.Location.endswith(".blend"))
|
return bool(external_style and external_style.Location and external_style.Location.endswith(".blend"))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _color_from_principled(cls, node: bpy.types.Node) -> tuple[tuple[float, float, float], float]:
|
||||||
|
color = cls._resolve_color_socket(node.inputs["Base Color"])
|
||||||
|
alpha_socket = node.inputs["Alpha"]
|
||||||
|
alpha_source = cls._upstream_color_source(alpha_socket)
|
||||||
|
if alpha_source and alpha_source[0] == "IMAGE":
|
||||||
|
pixels = alpha_source[1].pixels[:]
|
||||||
|
n = len(pixels) // 4
|
||||||
|
step = max(1, n // 4096)
|
||||||
|
a_sum = sum(pixels[i * 4 + 3] for i in range(0, n, step))
|
||||||
|
count = len(range(0, n, step)) or 1
|
||||||
|
transparency = 1.0 - (a_sum / count)
|
||||||
|
else:
|
||||||
|
transparency = 1.0 - alpha_socket.default_value
|
||||||
|
return color, transparency
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _color_from_shader_socket(
|
||||||
|
cls, socket: bpy.types.NodeSocket, seen: set[str] | None = None
|
||||||
|
) -> tuple[tuple[float, float, float], float] | None:
|
||||||
|
if seen is None:
|
||||||
|
seen = set()
|
||||||
|
for link in socket.links:
|
||||||
|
node = link.from_node
|
||||||
|
if node.name in seen:
|
||||||
|
continue
|
||||||
|
seen.add(node.name)
|
||||||
|
if node.type == "BSDF_PRINCIPLED":
|
||||||
|
return cls._color_from_principled(node)
|
||||||
|
if node.type in ("BSDF_DIFFUSE", "DIFFUSE_BSDF"):
|
||||||
|
return cls._resolve_color_socket(node.inputs["Color"]), 0.0
|
||||||
|
if node.type == "BSDF_GLASS":
|
||||||
|
return cls._resolve_color_socket(node.inputs["Color"]), 0.0
|
||||||
|
if node.type in ("MIX_SHADER", "ADD_SHADER"):
|
||||||
|
for inp in node.inputs:
|
||||||
|
if inp.type == "SHADER" and inp.is_linked:
|
||||||
|
result = cls._color_from_shader_socket(inp, seen)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_representative_material_color(
|
||||||
|
cls, material: bpy.types.Material
|
||||||
|
) -> tuple[tuple[float, float, float], float]:
|
||||||
|
if material.node_tree:
|
||||||
|
nodes = material.node_tree.nodes
|
||||||
|
output_node = next(
|
||||||
|
(n for n in nodes if n.type == "OUTPUT_MATERIAL" and n.is_active_output), None
|
||||||
|
) or next((n for n in nodes if n.type == "OUTPUT_MATERIAL"), None)
|
||||||
|
if output_node:
|
||||||
|
result = cls._color_from_shader_socket(output_node.inputs["Surface"])
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
# Fallback: scan all shader nodes if no output node or graph traversal found nothing
|
||||||
|
for node in nodes:
|
||||||
|
if node.type == "BSDF_PRINCIPLED":
|
||||||
|
return cls._color_from_principled(node)
|
||||||
|
for node in nodes:
|
||||||
|
if node.type in ("BSDF_DIFFUSE", "DIFFUSE_BSDF"):
|
||||||
|
return cls._resolve_color_socket(node.inputs["Color"]), 0.0
|
||||||
|
for node in nodes:
|
||||||
|
if node.type == "BSDF_GLASS":
|
||||||
|
return cls._resolve_color_socket(node.inputs["Color"]), 0.0
|
||||||
|
color = tuple(material.diffuse_color[:3])
|
||||||
|
transparency = 1.0 - material.diffuse_color[3]
|
||||||
|
return color, transparency
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _collect_upstream_sources(cls, socket: bpy.types.NodeSocket, seen: set[str]) -> list[tuple[str, object]]:
|
||||||
|
"""Recursively collect all upstream colour/image sources reachable from *socket*."""
|
||||||
|
results = []
|
||||||
|
for link in socket.links:
|
||||||
|
node = link.from_node
|
||||||
|
if node.name in seen:
|
||||||
|
continue
|
||||||
|
seen.add(node.name)
|
||||||
|
if node.type == "TEX_IMAGE":
|
||||||
|
results.append(("IMAGE", node.image))
|
||||||
|
elif node.type == "VALTORGB":
|
||||||
|
results.append(("COLORRAMP", node))
|
||||||
|
else:
|
||||||
|
for inp in node.inputs:
|
||||||
|
if inp.is_linked:
|
||||||
|
results.extend(cls._collect_upstream_sources(inp, seen))
|
||||||
|
return results
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _upstream_color_source(
|
||||||
|
cls, socket: bpy.types.NodeSocket, seen: set[str] | None = None
|
||||||
|
) -> tuple[str, object] | None:
|
||||||
|
sources = cls._collect_upstream_sources(socket, set() if seen is None else seen)
|
||||||
|
# Prefer a concrete image texture over a colour ramp (which may be greyscale/procedural).
|
||||||
|
for s in sources:
|
||||||
|
if s[0] == "IMAGE":
|
||||||
|
return s
|
||||||
|
for s in sources:
|
||||||
|
if s[0] == "COLORRAMP":
|
||||||
|
return s
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _resolve_color_socket(cls, socket: bpy.types.NodeSocket) -> tuple[float, float, float]:
|
||||||
|
source = cls._upstream_color_source(socket)
|
||||||
|
if source is None:
|
||||||
|
return tuple(socket.default_value[:3])
|
||||||
|
kind, obj = source
|
||||||
|
if kind == "IMAGE":
|
||||||
|
return cls._average_image_color(obj)
|
||||||
|
if kind == "COLORRAMP":
|
||||||
|
return cls._average_colorramp_color(obj)
|
||||||
|
return tuple(socket.default_value[:3])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _average_image_color(image: bpy.types.Image) -> tuple[float, float, float]:
|
||||||
|
pixels = image.pixels[:]
|
||||||
|
n = len(pixels) // 4
|
||||||
|
if n == 0:
|
||||||
|
return (0.5, 0.5, 0.5)
|
||||||
|
step = max(1, n // 4096)
|
||||||
|
r_sum = g_sum = b_sum = 0.0
|
||||||
|
count = 0
|
||||||
|
for i in range(0, n, step):
|
||||||
|
base = i * 4
|
||||||
|
r_sum += pixels[base]
|
||||||
|
g_sum += pixels[base + 1]
|
||||||
|
b_sum += pixels[base + 2]
|
||||||
|
count += 1
|
||||||
|
return (r_sum / count, g_sum / count, b_sum / count)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _average_colorramp_color(node: bpy.types.Node) -> tuple[float, float, float]:
|
||||||
|
elements = node.color_ramp.elements
|
||||||
|
if not elements:
|
||||||
|
return (0.5, 0.5, 0.5)
|
||||||
|
r = sum(e.color[0] for e in elements) / len(elements)
|
||||||
|
g = sum(e.color[1] for e in elements) / len(elements)
|
||||||
|
b = sum(e.color[2] for e in elements) / len(elements)
|
||||||
|
return (r, g, b)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_editing_styles(cls) -> bool:
|
def is_editing_styles(cls) -> bool:
|
||||||
props = cls.get_style_props()
|
props = cls.get_style_props()
|
||||||
@@ -674,9 +814,179 @@ class Style(bonsai.core.tool.Style):
|
|||||||
props = cls.get_material_style_props(blender_material)
|
props = cls.get_material_style_props(blender_material)
|
||||||
props.active_style_type = props.active_style_type
|
props.active_style_type = props.active_style_type
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_branch_outputs(
|
||||||
|
cls, material: bpy.types.Material
|
||||||
|
) -> tuple[bpy.types.ShaderNode | None, bpy.types.ShaderNode | None]:
|
||||||
|
"""Return (external_output_node, flat_output_node), or (None, None) if not dual-branch."""
|
||||||
|
if not material.node_tree:
|
||||||
|
return None, None
|
||||||
|
ext = material.node_tree.nodes.get("BIM_Output_External")
|
||||||
|
fast = material.node_tree.nodes.get("BIM_Output_Flat")
|
||||||
|
return ext, fast
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _remove_external_branch(cls, material: bpy.types.Material) -> None:
|
||||||
|
"""Remove all nodes reachable from BIM_Output_External (walks links backwards)."""
|
||||||
|
if not material.node_tree:
|
||||||
|
return
|
||||||
|
nodes = material.node_tree.nodes
|
||||||
|
output = nodes.get("BIM_Output_External")
|
||||||
|
if not output:
|
||||||
|
return
|
||||||
|
to_remove: set[str] = set()
|
||||||
|
stack = [output]
|
||||||
|
while stack:
|
||||||
|
node = stack.pop()
|
||||||
|
if node.name in to_remove:
|
||||||
|
continue
|
||||||
|
to_remove.add(node.name)
|
||||||
|
for inp in node.inputs:
|
||||||
|
for link in inp.links:
|
||||||
|
stack.append(link.from_node)
|
||||||
|
for name in list(to_remove):
|
||||||
|
n = nodes.get(name)
|
||||||
|
if n:
|
||||||
|
nodes.remove(n)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _build_flat_branch_nodes(cls, material: bpy.types.Material) -> bpy.types.ShaderNode:
|
||||||
|
"""Add a Principled BSDF flat-branch to material's existing node tree.
|
||||||
|
|
||||||
|
Reads IfcSurfaceStyleRendering or IfcSurfaceStyleShading from the linked IFC entity.
|
||||||
|
Defaults to a white BSDF when no IFC shading data is available.
|
||||||
|
Returns the new Material Output node (named BIM_Output_Flat, is_active_output=False).
|
||||||
|
"""
|
||||||
|
from mathutils import Vector
|
||||||
|
|
||||||
|
style_elements = cls.get_style_elements(material)
|
||||||
|
nodes = material.node_tree.nodes
|
||||||
|
links = material.node_tree.links
|
||||||
|
|
||||||
|
bsdf = nodes.new("ShaderNodeBsdfPrincipled")
|
||||||
|
bsdf.location = Vector((10, -600))
|
||||||
|
output = nodes.new("ShaderNodeOutputMaterial")
|
||||||
|
output.name = "BIM_Output_Flat"
|
||||||
|
output.location = Vector((300, -600))
|
||||||
|
output.is_active_output = False
|
||||||
|
links.new(bsdf.outputs["BSDF"], output.inputs["Surface"])
|
||||||
|
|
||||||
|
rendering_style = None
|
||||||
|
shading_only = None
|
||||||
|
for surface_style in style_elements.values():
|
||||||
|
if surface_style.is_a() == "IfcSurfaceStyleShading":
|
||||||
|
shading_only = surface_style
|
||||||
|
elif surface_style.is_a("IfcSurfaceStyleRendering"):
|
||||||
|
rendering_style = surface_style
|
||||||
|
shading_only = None
|
||||||
|
|
||||||
|
if rendering_style:
|
||||||
|
d = tool.Loader.surface_style_to_dict(rendering_style)
|
||||||
|
if d.get("DiffuseColour"):
|
||||||
|
ctype, cval = d["DiffuseColour"]
|
||||||
|
if ctype == "IfcColourRgb":
|
||||||
|
bsdf.inputs["Base Color"].default_value = cval + (1,)
|
||||||
|
solid_color = cval
|
||||||
|
else:
|
||||||
|
cval = tuple(v * cval for v in d["SurfaceColour"])
|
||||||
|
bsdf.inputs["Base Color"].default_value = cval + (1,)
|
||||||
|
solid_color = cval
|
||||||
|
else:
|
||||||
|
r, g, b = d["SurfaceColour"]
|
||||||
|
bsdf.inputs["Base Color"].default_value = (r, g, b, 1.0)
|
||||||
|
solid_color = (r, g, b)
|
||||||
|
if d.get("SpecularColour"):
|
||||||
|
ctype, cval = d["SpecularColour"]
|
||||||
|
if ctype == "IfcNormalisedRatioMeasure":
|
||||||
|
bsdf.inputs["Metallic"].default_value = cval
|
||||||
|
if d.get("SpecularHighlight"):
|
||||||
|
bsdf.inputs["Roughness"].default_value = d["SpecularHighlight"]
|
||||||
|
transparency = d.get("Transparency") or 0.0
|
||||||
|
bsdf.inputs["Alpha"].default_value = 1 - transparency
|
||||||
|
if transparency > 0:
|
||||||
|
material.blend_method = "BLEND"
|
||||||
|
material.diffuse_color = solid_color + (1.0 - transparency,)
|
||||||
|
elif shading_only:
|
||||||
|
d = tool.Loader.surface_style_to_dict(shading_only)
|
||||||
|
r, g, b = d["SurfaceColour"]
|
||||||
|
alpha = 1 - (d.get("Transparency") or 0.0)
|
||||||
|
bsdf.inputs["Base Color"].default_value = (r, g, b, 1.0)
|
||||||
|
bsdf.inputs["Alpha"].default_value = alpha
|
||||||
|
if alpha < 1.0:
|
||||||
|
material.blend_method = "BLEND"
|
||||||
|
material.diffuse_color = (r, g, b, alpha)
|
||||||
|
# else: leave default white Principled BSDF
|
||||||
|
return output
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setup_dual_branch(cls, material: bpy.types.Material, ext_material: bpy.types.Material) -> bool:
|
||||||
|
"""Build a dual-branch node tree: flat branch from IFC data + external branch from ext_material.
|
||||||
|
|
||||||
|
Clears any existing nodes and builds both branches from scratch.
|
||||||
|
External branch output (BIM_Output_External) is set active — Pretty mode.
|
||||||
|
Flat branch output (BIM_Output_Flat) is inactive — Flat mode.
|
||||||
|
Returns True on success; False if no shader editor is available (falls back to single-branch).
|
||||||
|
"""
|
||||||
|
cls.set_use_nodes(material, True)
|
||||||
|
for n in material.node_tree.nodes[:]:
|
||||||
|
material.node_tree.nodes.remove(n)
|
||||||
|
|
||||||
|
cls._build_flat_branch_nodes(material)
|
||||||
|
|
||||||
|
ext_output = tool.Blender.copy_node_graph_additive(material, ext_material)
|
||||||
|
if not ext_output:
|
||||||
|
# No shader editor available: fall back to single-branch
|
||||||
|
tool.Blender.copy_node_graph(material, ext_material)
|
||||||
|
return False
|
||||||
|
|
||||||
|
ext_output.name = "BIM_Output_External"
|
||||||
|
ext_output.is_active_output = True
|
||||||
|
material["bim_dual_branch"] = True
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_external_branch(cls, material: bpy.types.Material, ext_material: bpy.types.Material) -> None:
|
||||||
|
"""Replace the external-branch nodes of an already dual-branch material."""
|
||||||
|
cls._remove_external_branch(material)
|
||||||
|
ext_output = tool.Blender.copy_node_graph_additive(material, ext_material)
|
||||||
|
if ext_output:
|
||||||
|
ext_output.name = "BIM_Output_External"
|
||||||
|
ext_output.is_active_output = True
|
||||||
|
fast = material.node_tree.nodes.get("BIM_Output_Flat")
|
||||||
|
if fast:
|
||||||
|
fast.is_active_output = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sync_flat_branch_shading(
|
||||||
|
cls, material: bpy.types.Material, surface_colour: tuple[float, float, float], transparency: float
|
||||||
|
) -> None:
|
||||||
|
"""Update the flat-branch Principled BSDF with new shading values.
|
||||||
|
|
||||||
|
Call this after creating or editing IfcSurfaceStyleShading so the flat branch
|
||||||
|
stays in sync without requiring a full setup_dual_branch rebuild.
|
||||||
|
"""
|
||||||
|
if not material.node_tree:
|
||||||
|
return
|
||||||
|
fast_output = material.node_tree.nodes.get("BIM_Output_Flat")
|
||||||
|
if not fast_output:
|
||||||
|
return
|
||||||
|
for link in fast_output.inputs["Surface"].links:
|
||||||
|
if link.from_node.type == "BSDF_PRINCIPLED":
|
||||||
|
bsdf = link.from_node
|
||||||
|
r, g, b = surface_colour
|
||||||
|
bsdf.inputs["Base Color"].default_value = (r, g, b, 1.0)
|
||||||
|
bsdf.inputs["Alpha"].default_value = 1.0 - transparency
|
||||||
|
break
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def switch_shading(cls, blender_material: bpy.types.Material, style_type: StyleType) -> None:
|
def switch_shading(cls, blender_material: bpy.types.Material, style_type: StyleType) -> None:
|
||||||
if style_type == "External":
|
if style_type == "External":
|
||||||
|
ext, fast = cls.get_branch_outputs(blender_material)
|
||||||
|
if ext and fast:
|
||||||
|
ext.is_active_output = True
|
||||||
|
fast.is_active_output = False
|
||||||
|
blender_material.update_tag()
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
bpy.ops.bim.activate_external_style(material_name=blender_material.name)
|
bpy.ops.bim.activate_external_style(material_name=blender_material.name)
|
||||||
except RuntimeError as error:
|
except RuntimeError as error:
|
||||||
@@ -684,21 +994,41 @@ class Style(bonsai.core.tool.Style):
|
|||||||
return
|
return
|
||||||
raise error
|
raise error
|
||||||
elif style_type == "Shading":
|
elif style_type == "Shading":
|
||||||
|
ext, fast = cls.get_branch_outputs(blender_material)
|
||||||
|
if ext and fast:
|
||||||
|
fast.is_active_output = True
|
||||||
|
ext.is_active_output = False
|
||||||
|
blender_material.update_tag()
|
||||||
|
return
|
||||||
style_elements = tool.Style.get_style_elements(blender_material)
|
style_elements = tool.Style.get_style_elements(blender_material)
|
||||||
rendering_style = None
|
rendering_style = None
|
||||||
texture_style = None
|
texture_style = None
|
||||||
|
shading_only_style = None
|
||||||
|
|
||||||
for surface_style in style_elements.values():
|
for surface_style in style_elements.values():
|
||||||
if surface_style.is_a() == "IfcSurfaceStyleShading":
|
if surface_style.is_a() == "IfcSurfaceStyleShading":
|
||||||
|
shading_only_style = surface_style
|
||||||
tool.Loader.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
|
||||||
|
shading_only_style = None # rendering overrides shading-only path
|
||||||
tool.Loader.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:
|
||||||
tool.Loader.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
|
tool.Loader.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
|
||||||
|
elif shading_only_style and not rendering_style:
|
||||||
|
# create a minimal Principled BSDF so Material Preview/Rendered shows the colour instead of white.
|
||||||
|
tool.Style.set_use_nodes(blender_material, True)
|
||||||
|
tool.Loader.restart_material_node_tree(blender_material)
|
||||||
|
bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED")
|
||||||
|
if bsdf:
|
||||||
|
r, g, b, a = blender_material.diffuse_color
|
||||||
|
bsdf.inputs["Base Color"].default_value = (r, g, b, 1)
|
||||||
|
bsdf.inputs["Alpha"].default_value = a
|
||||||
|
if a < 1.0:
|
||||||
|
blender_material.blend_method = "BLEND"
|
||||||
else:
|
else:
|
||||||
assert False, f"Invalid style type found: {style_type}"
|
assert False, f"Invalid style type found: {style_type}"
|
||||||
|
|
||||||
@@ -744,3 +1074,36 @@ class Style(bonsai.core.tool.Style):
|
|||||||
elements = ifcopenshell.util.element.get_elements_by_style(tool.Ifc.get(), style)
|
elements = ifcopenshell.util.element.get_elements_by_style(tool.Ifc.get(), style)
|
||||||
objects = [tool.Ifc.get_object(e) for e in elements]
|
objects = [tool.Ifc.get_object(e) for e in elements]
|
||||||
tool.Geometry.reload_representation(objects)
|
tool.Geometry.reload_representation(objects)
|
||||||
|
|
||||||
|
_last_shading_type: str | None = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def restore_material_style_types(cls, shading_type: str) -> None:
|
||||||
|
"""Set each IFC material's active_style_type to the richest available for the given viewport mode.
|
||||||
|
|
||||||
|
In SOLID mode all materials use "Shading".
|
||||||
|
In MATERIAL_PREVIEW / RENDERED, materials with an external .blend style use "External"
|
||||||
|
unless prefer_ifc_shading is set on that material.
|
||||||
|
"""
|
||||||
|
if cls._last_shading_type == shading_type:
|
||||||
|
return
|
||||||
|
cls._last_shading_type = shading_type
|
||||||
|
|
||||||
|
for material in bpy.data.materials:
|
||||||
|
if not tool.Blender.get_ifc_definition_id(material):
|
||||||
|
continue
|
||||||
|
props = cls.get_material_style_props(material)
|
||||||
|
style_elements = cls.get_style_elements(material)
|
||||||
|
if shading_type == "SOLID":
|
||||||
|
props.active_style_type = "Shading"
|
||||||
|
shading = style_elements.get("IfcSurfaceStyleRendering") or style_elements.get("IfcSurfaceStyleShading")
|
||||||
|
if shading:
|
||||||
|
d = tool.Loader.surface_style_to_dict(shading)
|
||||||
|
alpha = 1.0 - (d.get("Transparency") or 0.0)
|
||||||
|
material.diffuse_color = d["SurfaceColour"] + (alpha,)
|
||||||
|
else: # MATERIAL_PREVIEW or RENDERED
|
||||||
|
if cls.has_blender_external_style(style_elements) and not props.prefer_ifc_shading:
|
||||||
|
props.active_style_type = "External"
|
||||||
|
else:
|
||||||
|
props.active_style_type = "Shading"
|
||||||
|
material.update_tag()
|
||||||
|
|||||||
Reference in New Issue
Block a user