364 lines
16 KiB
Python
364 lines
16 KiB
Python
from imgui_bundle import imgui, imgui_ctx
|
||
|
||
class EditorPanelsRightMaterialMixin:
|
||
"""Auto-split mixin from editor_panels_right.py."""
|
||
|
||
def _ensure_material_edit_sessions(self):
|
||
if not hasattr(self, "_material_edit_sessions"):
|
||
self._material_edit_sessions = {}
|
||
return self._material_edit_sessions
|
||
|
||
def _sync_material_panel_target(self, node):
|
||
"""Reset per-node material UI state when selection changes."""
|
||
target_key = None
|
||
try:
|
||
target_key = getattr(node, "this", None) or id(node)
|
||
except Exception:
|
||
target_key = id(node)
|
||
|
||
if getattr(self, "_material_panel_target_key", None) == target_key:
|
||
return
|
||
|
||
self._material_panel_target_key = target_key
|
||
self._material_edit_sessions = {}
|
||
property_helpers = getattr(self.app, "property_helpers", None)
|
||
ensure_unique_fn = getattr(property_helpers, "_ensure_unique_materials_for_node", None)
|
||
if callable(ensure_unique_fn):
|
||
ensure_unique_fn(node)
|
||
|
||
def _ensure_node_materials_are_editable(self, node):
|
||
self._sync_material_panel_target(node)
|
||
ensure_unique_fn = getattr(self.app, "_ensure_unique_materials_for_node", None)
|
||
if callable(ensure_unique_fn):
|
||
try:
|
||
ensure_unique_fn(node)
|
||
except Exception:
|
||
pass
|
||
panel_materials_fn = getattr(self.app, "_get_panel_edit_materials_for_node", None)
|
||
if callable(panel_materials_fn):
|
||
try:
|
||
materials = panel_materials_fn(node)
|
||
if materials:
|
||
return materials
|
||
except Exception:
|
||
pass
|
||
return self.app._get_node_materials(node)
|
||
|
||
def _refresh_ssbo_runtime_for_material_node(self, node, preserve_selection=True):
|
||
ssbo_editor = getattr(self.app, "ssbo_editor", None)
|
||
if not ssbo_editor:
|
||
return
|
||
if not hasattr(ssbo_editor, "is_source_tree_node") or not ssbo_editor.is_source_tree_node(node):
|
||
return
|
||
if hasattr(ssbo_editor, "refresh_runtime_from_source"):
|
||
try:
|
||
ssbo_editor.refresh_runtime_from_source(preserve_selection=preserve_selection)
|
||
except Exception:
|
||
pass
|
||
|
||
def _begin_material_edit_session(self, node, session_key):
|
||
self._ensure_node_materials_are_editable(node)
|
||
sessions = self._ensure_material_edit_sessions()
|
||
sessions.setdefault(session_key, self.app._capture_node_material_snapshot(node))
|
||
|
||
def _record_material_snapshot_command(self, node, before_snapshot, after_snapshot):
|
||
if not hasattr(self.app, "command_manager") or not self.app.command_manager:
|
||
return
|
||
if self.app._material_snapshots_equal(before_snapshot, after_snapshot):
|
||
return
|
||
|
||
try:
|
||
from core.Command_System import MaterialStateCommand
|
||
|
||
self.app.command_manager.record_command(
|
||
MaterialStateCommand(
|
||
lambda state, target_node=node: self.app._apply_node_material_snapshot(target_node, state),
|
||
before_snapshot,
|
||
after_snapshot,
|
||
)
|
||
)
|
||
except Exception:
|
||
pass
|
||
|
||
def _finish_material_edit_session(self, node, session_key):
|
||
if not imgui.is_item_deactivated_after_edit():
|
||
return
|
||
sessions = self._ensure_material_edit_sessions()
|
||
before_snapshot = sessions.pop(session_key, None)
|
||
if before_snapshot is None:
|
||
return
|
||
after_snapshot = self.app._capture_node_material_snapshot(node)
|
||
self._record_material_snapshot_command(node, before_snapshot, after_snapshot)
|
||
self._refresh_ssbo_runtime_for_material_node(node)
|
||
|
||
def _apply_material_change_with_history(self, node, apply_callback):
|
||
self._ensure_node_materials_are_editable(node)
|
||
before_snapshot = self.app._capture_node_material_snapshot(node)
|
||
apply_callback()
|
||
after_snapshot = self.app._capture_node_material_snapshot(node)
|
||
self._record_material_snapshot_command(node, before_snapshot, after_snapshot)
|
||
self._refresh_ssbo_runtime_for_material_node(node)
|
||
|
||
def _select_texture_for_material_with_history(self, node, material, texture_type):
|
||
self._ensure_node_materials_are_editable(node)
|
||
before_snapshot = self.app._capture_node_material_snapshot(node)
|
||
changed = self._select_texture_for_material(node, material, texture_type)
|
||
if not changed:
|
||
return False
|
||
after_snapshot = self.app._capture_node_material_snapshot(node)
|
||
self._record_material_snapshot_command(node, before_snapshot, after_snapshot)
|
||
self._refresh_ssbo_runtime_for_material_node(node)
|
||
return True
|
||
|
||
def _draw_appearance_properties(self, node):
|
||
"""绘制材质属性(Unity风格主材质入口)。"""
|
||
materials = self._ensure_node_materials_are_editable(node)
|
||
if not materials:
|
||
fallback_material = self.app._ensure_material_for_node(node)
|
||
materials = [fallback_material] if fallback_material else []
|
||
if not materials:
|
||
imgui.text_colored((1.0, 0.5, 0.5, 1.0), "无法获取材质")
|
||
return
|
||
material = materials[0]
|
||
|
||
# 历史上可能通过 node.setColor 留下了额外染色,先清掉避免与材质主颜色打架
|
||
try:
|
||
if node.hasColor():
|
||
node.clearColor()
|
||
if hasattr(node, "clearColorScale"):
|
||
node.clearColorScale()
|
||
except Exception:
|
||
pass
|
||
|
||
base_color = self.app._get_material_base_color(material)
|
||
|
||
def apply_primary_color(color):
|
||
for current_material in materials:
|
||
self.app._set_material_base_color(current_material, color)
|
||
if self.app._get_material_surface_type(current_material) == 3:
|
||
self.app._set_material_opacity(node, current_material, color[3])
|
||
else:
|
||
self.app._sync_material_node_runtime(node, current_material, refresh_ssbo_runtime=False)
|
||
self._refresh_ssbo_runtime_for_material_node(node)
|
||
|
||
def apply_surface_type(surface_type):
|
||
for current_material in materials:
|
||
self.app._set_material_surface_type(
|
||
node,
|
||
current_material,
|
||
surface_type,
|
||
refresh_pipeline=False,
|
||
)
|
||
if materials:
|
||
self.app._apply_material_surface_state(node, materials[0])
|
||
self.app._refresh_pipeline_material_mode(node, materials[0])
|
||
|
||
def apply_opacity(opacity):
|
||
for current_material in materials:
|
||
self.app._set_material_opacity(node, current_material, opacity)
|
||
|
||
imgui.text("主颜色")
|
||
changed, new_color = imgui.color_edit4(
|
||
"##material_base_color",
|
||
base_color,
|
||
imgui.ColorEditFlags_.display_rgb.value,
|
||
)
|
||
if imgui.is_item_activated():
|
||
self._begin_material_edit_session(node, "appearance_primary_color")
|
||
if changed:
|
||
apply_primary_color(new_color)
|
||
self._finish_material_edit_session(node, "appearance_primary_color")
|
||
|
||
imgui.same_line()
|
||
if imgui.button("颜色选择器##material_color_picker"):
|
||
self.show_color_picker(
|
||
target_object=None,
|
||
property_name=None,
|
||
initial_color=base_color,
|
||
callback=lambda color: self._apply_material_change_with_history(
|
||
node,
|
||
lambda: apply_primary_color(color),
|
||
),
|
||
)
|
||
|
||
surface_options = [
|
||
("不透明", 0),
|
||
("自发光", 1),
|
||
("透明", 3),
|
||
]
|
||
current_surface = self.app._get_material_surface_type(material)
|
||
current_surface_index = next(
|
||
(index for index, (_, value) in enumerate(surface_options) if value == current_surface),
|
||
0,
|
||
)
|
||
|
||
imgui.text("表面类型")
|
||
changed, selected_index = imgui.combo(
|
||
"##material_surface_type",
|
||
current_surface_index,
|
||
[label for label, _ in surface_options],
|
||
)
|
||
if changed:
|
||
self._apply_material_change_with_history(
|
||
node,
|
||
lambda: apply_surface_type(surface_options[selected_index][1]),
|
||
)
|
||
current_surface = surface_options[selected_index][1]
|
||
|
||
if self.app._get_material_surface_type(material) == 3:
|
||
opacity = self.app._get_material_opacity(material)
|
||
transparency = 1.0 - opacity
|
||
changed, new_transparency = imgui.slider_float("透明度", transparency, 0.0, 1.0)
|
||
if imgui.is_item_activated():
|
||
self._begin_material_edit_session(node, "appearance_opacity")
|
||
if changed:
|
||
apply_opacity(1.0 - new_transparency)
|
||
self._finish_material_edit_session(node, "appearance_opacity")
|
||
|
||
imgui.separator()
|
||
|
||
# 详细材质属性
|
||
self._draw_material_properties(node)
|
||
|
||
def _draw_material_properties(self, node):
|
||
"""绘制材质属性"""
|
||
materials = self._ensure_node_materials_are_editable(node)
|
||
|
||
if not materials:
|
||
imgui.text_colored((0.5, 0.5, 0.5, 1.0), "无材质")
|
||
return
|
||
|
||
for i, material in enumerate(materials):
|
||
material_name = material.get_name() if hasattr(material, 'get_name') and material.get_name() else f"材质{i + 1}"
|
||
|
||
if imgui.collapsing_header(f"材质: {material_name}"):
|
||
# PBR属性
|
||
imgui.text("PBR")
|
||
if hasattr(material, 'roughness') and material.roughness is not None:
|
||
try:
|
||
roughness_value = float(material.roughness)
|
||
changed, new_roughness = imgui.slider_float(f"粗糙度##rough_{i}", roughness_value, 0.0, 1.0)
|
||
if imgui.is_item_activated():
|
||
self._begin_material_edit_session(node, f"material_{i}_roughness")
|
||
if changed:
|
||
self._update_material_roughness(material, new_roughness, node)
|
||
self._finish_material_edit_session(node, f"material_{i}_roughness")
|
||
except:
|
||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "粗糙度: 不可用")
|
||
|
||
if hasattr(material, 'metallic') and material.metallic is not None:
|
||
try:
|
||
metallic_value = float(material.metallic)
|
||
changed, new_metallic = imgui.slider_float(f"金属性##metal_{i}", metallic_value, 0.0, 1.0)
|
||
if imgui.is_item_activated():
|
||
self._begin_material_edit_session(node, f"material_{i}_metallic")
|
||
if changed:
|
||
self._update_material_metallic(material, new_metallic, node)
|
||
self._finish_material_edit_session(node, f"material_{i}_metallic")
|
||
except:
|
||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "金属性: 不可用")
|
||
|
||
if hasattr(material, 'refractive_index') and material.refractive_index is not None:
|
||
try:
|
||
ior_value = float(material.refractive_index)
|
||
changed, new_ior = imgui.slider_float(f"折射率##ior_{i}", ior_value, 1.0, 3.0)
|
||
if imgui.is_item_activated():
|
||
self._begin_material_edit_session(node, f"material_{i}_ior")
|
||
if changed:
|
||
self._update_material_ior(material, new_ior, node)
|
||
self._finish_material_edit_session(node, f"material_{i}_ior")
|
||
except:
|
||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "折射率: 不可用")
|
||
|
||
# 材质预设
|
||
imgui.text("材质预设")
|
||
presets = ["默认", "金属", "塑料", "玻璃", "木材", "混凝土"]
|
||
current_preset = 0 # 默认选择
|
||
|
||
if imgui.begin_combo(f"预设##preset_{i}", presets[current_preset]):
|
||
for j, preset_name in enumerate(presets):
|
||
if imgui.selectable(preset_name, j == current_preset):
|
||
self._apply_material_change_with_history(
|
||
node,
|
||
lambda selected_preset=preset_name: (
|
||
self._apply_material_preset(material, selected_preset, node),
|
||
self._apply_material_surface_state(node, material),
|
||
self.app._refresh_pipeline_material_mode(node, material),
|
||
),
|
||
)
|
||
imgui.end_combo()
|
||
|
||
# 纹理信息
|
||
imgui.text("纹理贴图")
|
||
if imgui.button(f"选择漫反射贴图##diffuse_{i}"):
|
||
self._select_texture_for_material_with_history(node, material, "diffuse")
|
||
|
||
imgui.same_line()
|
||
if imgui.button(f"选择法线贴图##normal_{i}"):
|
||
self._select_texture_for_material_with_history(node, material, "normal")
|
||
|
||
imgui.same_line()
|
||
if imgui.button(f"选择粗糙度贴图##roughness_{i}"):
|
||
self._select_texture_for_material_with_history(node, material, "roughness")
|
||
|
||
if imgui.button(f"选择金属性贴图##metallic_{i}"):
|
||
self._select_texture_for_material_with_history(node, material, "metallic")
|
||
|
||
imgui.same_line()
|
||
if imgui.button(f"选择自发光贴图##emission_{i}"):
|
||
self._select_texture_for_material_with_history(node, material, "emission")
|
||
|
||
imgui.same_line()
|
||
if imgui.button(f"清除所有贴图##clear_{i}"):
|
||
self._apply_material_change_with_history(node, lambda: self._clear_all_textures(node))
|
||
|
||
# 显示当前纹理信息
|
||
self._display_current_textures(node, material)
|
||
|
||
imgui.separator()
|
||
if imgui.button("应用材质"):
|
||
self._apply_material_to_node(node)
|
||
self._refresh_ssbo_runtime_for_material_node(node)
|
||
imgui.same_line()
|
||
if imgui.button("重置材质"):
|
||
self._apply_material_change_with_history(node, lambda: self._reset_material(node))
|
||
|
||
def _draw_shading_model_panel(self, node, material, material_index):
|
||
"""绘制着色模型选择面板"""
|
||
try:
|
||
imgui.text("着色模型")
|
||
|
||
shading_models = [
|
||
("默认", 0),
|
||
("自发光", 1),
|
||
("透明", 3),
|
||
]
|
||
current_model = self.app._get_material_surface_type(material)
|
||
current_index = next((idx for idx, (_, value) in enumerate(shading_models) if value == current_model), 0)
|
||
|
||
if imgui.begin_combo(f"着色模型##shading_{material_index}", shading_models[current_index][0]):
|
||
for index, (model_name, model_value) in enumerate(shading_models):
|
||
if imgui.selectable(model_name, index == current_index):
|
||
self.app._set_material_surface_type(node, material, model_value)
|
||
imgui.end_combo()
|
||
|
||
if self.app._get_material_surface_type(material) == 3:
|
||
imgui.text("透明度设置")
|
||
try:
|
||
current_opacity = self.app._get_material_opacity(material)
|
||
current_transparency = 1.0 - current_opacity
|
||
changed, new_transparency = imgui.slider_float(
|
||
f"透明度##opacity_{material_index}",
|
||
current_transparency,
|
||
0.0,
|
||
1.0,
|
||
)
|
||
if changed:
|
||
self.app._set_material_opacity(node, material, 1.0 - new_transparency)
|
||
except:
|
||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "透明度控制不可用")
|
||
|
||
except Exception as e:
|
||
print(f"绘制着色模型面板失败: {e}")
|
||
|