Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/bonsai/bonsai/bim/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,9 +335,10 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None


def viewport_shading_changed_callback(area: bpy.types.Area) -> None:
shading = area.spaces.active.shading.type
if shading == "RENDERED":
tool.Style.get_style_props().active_style_type = "External"
shading_type = area.spaces.active.shading.type
tool.Style.restore_material_style_types(shading_type)
if shading_type == "SOLID":
area.spaces.active.shading.color_type = "MATERIAL"


def subscribe_to_viewport_shading_changes():
Expand Down
4 changes: 0 additions & 4 deletions src/bonsai/bonsai/bim/import_ifc.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,6 @@ def load_texture_maps(self, shape_has_openings: bool) -> None:
if shape_has_openings and coords.is_a("IfcIndexedTextureMap"):
continue
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:
if not self.mesh["ios_materials"]:
Expand Down
4 changes: 4 additions & 0 deletions src/bonsai/bonsai/bim/module/project/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,10 @@ def execute(self, context):
tool.Project.set_default_modeling_dimensions()
tool.Root.reload_grid_decorator()
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"}

def get_decomposition_elements(self) -> set[ifcopenshell.entity_instance]:
Expand Down
2 changes: 2 additions & 0 deletions src/bonsai/bonsai/bim/module/style/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
operator.SelectByStyle,
operator.SelectStyleInStylesUI,
operator.SetAssetMaterialToExternalStyle,
operator.SuggestShadeFromExternalStyle,
operator.UnlinkStyle,
operator.TogglePreferIfcShading,
operator.UpdateCurrentStyle,
operator.UpdateStyleColours,
operator.UpdateStyleTextures,
Expand Down
159 changes: 159 additions & 0 deletions src/bonsai/bonsai/bim/module/style/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,165 @@ def set_prop_group(prop_group):
set_prop(prop_name)


class TogglePreferIfcShading(bpy.types.Operator):
bl_idname = "bim.toggle_prefer_ifc_shading"
bl_label = "Toggle Fast/Pretty"
bl_description = (
"Toggle between Fast (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 source_mat.BIMStyleProperties.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)]
wm.progress_begin(0, max(len(ifc_mats), 1))
try:
for i, mat in enumerate(ifc_mats):
tool.Style.get_material_style_props(mat).prefer_ifc_shading = new_value
wm.progress_update(i)
finally:
wm.progress_end()
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"})

def invoke(self, context, event):
if event.alt:
self.all_styles = True
return self.execute(context)

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
try:
for i, (mat, style_elements) in enumerate(candidates):
wm.progress_update(i)
if self._apply_to_material(mat, style_elements):
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)
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) -> 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("/")

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}')
return False
if not db["data_block"]:
self.report({"WARNING"}, f'Could not load external style for "{material.name}": {db["msg"]}')
return False

ext_mat = db["data_block"]
surface_colour, transparency = tool.Style.get_representative_material_color(ext_mat)
bpy.data.materials.remove(ext_mat)

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,
)
return True


class DisableEditingStyles(bpy.types.Operator):
bl_idname = "bim.disable_editing_styles"
bl_options = {"REGISTER", "UNDO"}
Expand Down
66 changes: 55 additions & 11 deletions src/bonsai/bonsai/bim/module/style/prop.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,19 @@ def update_shader_graph(self: Union["Texture", "BIMStylesProperties"], context:
tool.Loader.create_surface_style_with_textures(material, shading_data, textures_data)


def _make_clear_null_updater(null_prop: str):
def _update(self: "BIMStylesProperties", context: bpy.types.Context) -> None:
self[null_prop] = False
update_shader_graph(self, context)

return _update


update_diffuse_colour = _make_clear_null_updater("is_diffuse_colour_null")
update_specular_colour = _make_clear_null_updater("is_specular_colour_null")
update_specular_highlight_value = _make_clear_null_updater("is_specular_highlight_null")


UV_MODES = [
("UV", "UV", _("Actual UV data presented on the geometry")),
("Generated", "Generated", _("Automatically-generated UV from the vertex positions of the mesh")),
Expand Down Expand Up @@ -221,24 +234,29 @@ class BIMStylesProperties(PropertyGroup):
transparency: bpy.props.FloatProperty(
name="Transparency", default=0.0, min=0.0, max=1.0, update=update_shader_graph
)
# TODO: do something on null?
is_diffuse_colour_null: BoolProperty(name="Is Null")
is_diffuse_colour_null: BoolProperty(name="Is Null", update=update_shader_graph)
diffuse_colour_class: EnumProperty(
items=[(x, x, "") for x in get_args(ColourClass)],
name="Diffuse Colour Class",
update=update_shader_graph,
update=update_diffuse_colour,
)
diffuse_colour: bpy.props.FloatVectorProperty(
name="Diffuse Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3, update=update_shader_graph
name="Diffuse Colour",
subtype="COLOR",
default=(1, 1, 1),
min=0.0,
max=1.0,
size=3,
update=update_diffuse_colour,
)
diffuse_colour_ratio: bpy.props.FloatProperty(
name="Diffuse Ratio", default=0.0, min=0.0, max=1.0, update=update_shader_graph
name="Diffuse Ratio", default=0.0, min=0.0, max=1.0, update=update_diffuse_colour
)
is_specular_colour_null: BoolProperty(name="Is Null")
is_specular_colour_null: BoolProperty(name="Is Null", update=update_shader_graph)
specular_colour_class: EnumProperty(
items=[(x, x, "") for x in get_args(ColourClass)],
name="Specular Colour Class",
update=update_shader_graph,
update=update_specular_colour,
default="IfcNormalisedRatioMeasure",
)
specular_colour: bpy.props.FloatVectorProperty(
Expand All @@ -248,24 +266,24 @@ class BIMStylesProperties(PropertyGroup):
min=0.0,
max=1.0,
size=3,
update=update_shader_graph,
update=update_specular_colour,
)
specular_colour_ratio: bpy.props.FloatProperty(
name="Specular Ratio",
description="Used as Metallic value in PHYSICAL Reflectance Method",
default=0.0,
min=0.0,
max=1.0,
update=update_shader_graph,
update=update_specular_colour,
)
is_specular_highlight_null: BoolProperty(name="Is Null")
is_specular_highlight_null: BoolProperty(name="Is Null", update=update_shader_graph)
specular_highlight: bpy.props.FloatProperty(
name="Specular Highlight",
description="Used as Roughness value in PHYSICAL Reflectance Method",
default=0.0,
min=0.0,
max=1.0,
update=update_shader_graph,
update=update_specular_highlight_value,
)
reflectance_method: EnumProperty(
name="Reflectance Method",
Expand Down Expand Up @@ -347,6 +365,22 @@ def update_shading_style(self: "BIMStyleProperties", context: bpy.types.Context)
tool.Style.switch_shading(blender_material, self.active_style_type)


def update_prefer_ifc_shading(self: "BIMStyleProperties", context: bpy.types.Context) -> None:
blender_material = self.id_data
style_elements = tool.Style.get_style_elements(blender_material)
has_external = tool.Style.has_blender_external_style(style_elements)
if self.prefer_ifc_shading:
self.active_style_type = "Shading"
tool.Style.switch_shading(blender_material, "Shading")
elif has_external:
self.active_style_type = "External"
tool.Style.switch_shading(blender_material, "External")
else:
self.active_style_type = "Shading"
tool.Style.switch_shading(blender_material, "Shading")
blender_material.update_tag()


class BIMStyleProperties(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
active_style_type: EnumProperty(
Expand All @@ -356,9 +390,19 @@ class BIMStyleProperties(PropertyGroup):
default="Shading",
update=update_shading_style,
)
prefer_ifc_shading: BoolProperty(
name="Fast / Pretty",
description=(
"Toggle between Fast (IFC-native shading) and Pretty (external .blend style). "
"When set to Fast, viewport switches to Material Preview or Rendered will not activate the external style."
),
default=True,
update=update_prefer_ifc_shading,
)
is_renaming: BoolProperty(description="Used to prevent triggering handler callback.", default=False)

if TYPE_CHECKING:
ifc_definition_id: int
active_style_type: tool.Style.StyleType
prefer_ifc_shading: bool
is_renaming: bool
Loading
Loading