编辑操作,地形生成,材质系统问题修复
This commit is contained in:
parent
2766fb9faa
commit
69d83c6ab5
BIN
Resources/textures/布料 针织法线贴图.jpg
Normal file
BIN
Resources/textures/布料 针织法线贴图.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
BIN
Resources/textures/针织 编织 布料法线纹理贴图.jpg
Normal file
BIN
Resources/textures/针织 编织 布料法线纹理贴图.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 MiB |
@ -270,10 +270,10 @@ class DeleteNodeCommand(Command):
|
||||
elif light_type == "point_light" and hasattr(scene_manager,
|
||||
'Pointlight') and self.node in scene_manager.Pointlight:
|
||||
scene_manager.Pointlight.remove(self.node)
|
||||
elif self.node_type == "IMPORTED_MODEL_NODE" and hasattr(scene_manager,
|
||||
'models') and self.node in scene_manager.models:
|
||||
if hasattr(scene_manager, 'models') and self.node in scene_manager.models:
|
||||
scene_manager.models.remove(self.node)
|
||||
elif self.node_type.startswith("GUI_") and hasattr(self.world,
|
||||
|
||||
if self.node_type.startswith("GUI_") and hasattr(self.world,
|
||||
'gui_elements') and self.node in self.world.gui_elements:
|
||||
self.world.gui_elements.remove(self.node)
|
||||
elif self.node_type == "CESIUM_TILESET_NODE":
|
||||
@ -286,49 +286,43 @@ class DeleteNodeCommand(Command):
|
||||
for i in reversed(tilesets_to_remove):
|
||||
del scene_manager.tilesets[i]
|
||||
|
||||
# 从场景图中移除节点
|
||||
# 从场景图中移除节点,使用 detachNode 而不是 removeNode 以便可以撤销
|
||||
if self.node and not self.node.isEmpty():
|
||||
self.node.removeNode()
|
||||
self.node.detachNode()
|
||||
|
||||
def undo(self):
|
||||
"""
|
||||
撤销删除操作(重新创建节点)
|
||||
撤销删除操作(恢复旧节点)
|
||||
"""
|
||||
try:
|
||||
# 使用场景管理器重建节点
|
||||
if self.node and not self.node.isEmpty():
|
||||
# 直接将节点挂载回原父节点
|
||||
self.node.reparentTo(self.parent_node)
|
||||
|
||||
# 恢复到相应的管理器列表中
|
||||
if self.world and hasattr(self.world, 'scene_manager'):
|
||||
scene_manager = self.world.scene_manager
|
||||
if self.node_type == "LIGHT_NODE":
|
||||
if self.node.hasTag("light_type"):
|
||||
light_type = self.node.getTag("light_type")
|
||||
if light_type == "spot_light" and hasattr(scene_manager, 'Spotlight') and self.node not in scene_manager.Spotlight:
|
||||
scene_manager.Spotlight.append(self.node)
|
||||
elif light_type == "point_light" and hasattr(scene_manager, 'Pointlight') and self.node not in scene_manager.Pointlight:
|
||||
scene_manager.Pointlight.append(self.node)
|
||||
|
||||
# 创建节点数据字典
|
||||
node_data = {
|
||||
'name': self.node_name,
|
||||
'node_type': self.node_type,
|
||||
'pos': (self.node_pos.x, self.node_pos.y, self.node_pos.z),
|
||||
'hpr': (self.node_hpr.x, self.node_hpr.y, self.node_hpr.z),
|
||||
'scale': (self.node_scale.x, self.node_scale.y, self.node_scale.z),
|
||||
'tags': self.node_tags
|
||||
}
|
||||
if hasattr(scene_manager, 'models') and self.node not in scene_manager.models:
|
||||
scene_manager.models.append(self.node)
|
||||
|
||||
# 添加额外数据
|
||||
if self.extra_data:
|
||||
if self.node_type.startswith("GUI_"):
|
||||
node_data['gui_data'] = self.extra_data
|
||||
elif self.node_type == "LIGHT_NODE":
|
||||
node_data['light_data'] = self.extra_data.get('light_data', {})
|
||||
if self.node_type.startswith("GUI_") and hasattr(self.world, 'gui_elements') and self.node not in self.world.gui_elements:
|
||||
self.world.gui_elements.append(self.node)
|
||||
elif self.node_type == "CESIUM_TILESET_NODE":
|
||||
node_data['tileset_url'] = self.extra_data.get('tileset_url', '')
|
||||
# 简单恢复到 tilesets
|
||||
if hasattr(scene_manager, 'tilesets'):
|
||||
scene_manager.tilesets.append({'node': self.node, 'url': self.extra_data.get('tileset_url', '')})
|
||||
|
||||
# 重建节点
|
||||
new_node = scene_manager.recreateNodeFromData(node_data, self.parent_node)
|
||||
|
||||
if new_node:
|
||||
print(f"✅ 成功撤销删除操作,节点 {self.node_name} 已恢复")
|
||||
# 更新节点引用
|
||||
self.node = new_node
|
||||
else:
|
||||
print(f"❌ 撤销删除操作失败,无法重建节点 {self.node_name}")
|
||||
else:
|
||||
print("❌ 无法撤销删除操作,缺少场景管理器引用")
|
||||
print("❌ 无法撤销删除操作,节点引用已丢失")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 撤销删除操作时出错: {e}")
|
||||
@ -424,7 +418,7 @@ class CreateNodeCommand(Command):
|
||||
撤销创建节点操作
|
||||
"""
|
||||
if self.created_node:
|
||||
self.created_node.removeNode()
|
||||
self.created_node.detachNode()
|
||||
|
||||
def redo(self):
|
||||
"""
|
||||
|
||||
@ -39,13 +39,14 @@ class TerrainManager:
|
||||
|
||||
# 获取树形控件
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not tree_widget:
|
||||
print("❌ 无法访问树形控件")
|
||||
return None
|
||||
|
||||
# 获取目标父节点列表
|
||||
target_parents = None
|
||||
if tree_widget:
|
||||
target_parents = tree_widget.get_target_parents_for_creation()
|
||||
|
||||
if not target_parents:
|
||||
if hasattr(self.world, 'render'):
|
||||
target_parents = [(None, self.world.render)]
|
||||
else:
|
||||
print("❌ 没有找到有效的父节点")
|
||||
return None
|
||||
|
||||
@ -143,18 +144,34 @@ class TerrainManager:
|
||||
|
||||
self.terrains.append(terrain_info)
|
||||
|
||||
print(f"✅ 为 {parent_item.text(0)} 创建高度图地形: {terrain_name}")
|
||||
parent_name = parent_item.text(0) if parent_item else "root"
|
||||
print(f"✅ 为 {parent_name} 创建高度图地形: {terrain_name}")
|
||||
|
||||
# 在Qt树形控件中添加对应节点
|
||||
qt_item = None
|
||||
if tree_widget and parent_item:
|
||||
qt_item = tree_widget.add_node_to_tree_widget(terrain_node, parent_item, "TERRAIN_NODE")
|
||||
|
||||
if qt_item:
|
||||
created_terrains.append((terrain_node, qt_item))
|
||||
else:
|
||||
created_terrains.append((terrain_node, None))
|
||||
print("⚠️ Qt树节点添加失败,但Panda3D对象已创建")
|
||||
print("⚠️ Qt树节点添加跳过或失败,但Panda3D对象已创建")
|
||||
|
||||
# 添加到场景管理器(ImGui环境使用)
|
||||
if hasattr(self.world, 'scene_manager') and self.world.scene_manager:
|
||||
if not hasattr(self.world.scene_manager, 'models'):
|
||||
self.world.scene_manager.models = []
|
||||
if terrain_node not in self.world.scene_manager.models:
|
||||
self.world.scene_manager.models.append(terrain_node)
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 为 {parent_item.text(0)} 创建高度图地形: {str(e)}")
|
||||
parent_name = parent_item.text(0) if parent_item else "root"
|
||||
print(f"❌ 为 {parent_name} 创建高度图地形失败: {str(e)}")
|
||||
return None
|
||||
|
||||
# 处理创建结果
|
||||
@ -191,13 +208,14 @@ class TerrainManager:
|
||||
|
||||
# 获取树形控件
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not tree_widget:
|
||||
print("❌ 无法访问树形控件")
|
||||
return None
|
||||
|
||||
# 获取目标父节点列表
|
||||
target_parents = None
|
||||
if tree_widget:
|
||||
target_parents = tree_widget.get_target_parents_for_creation()
|
||||
|
||||
if not target_parents:
|
||||
if hasattr(self.world, 'render'):
|
||||
target_parents = [(None, self.world.render)]
|
||||
else:
|
||||
print("❌ 没有找到有效的父节点")
|
||||
return None
|
||||
|
||||
@ -261,6 +279,8 @@ class TerrainManager:
|
||||
# 添加材质
|
||||
self._applyTerrainMaterial(terrain_node)
|
||||
|
||||
terrain_node.setPythonTag("selectable", True)
|
||||
|
||||
# 保存地形信息(包括高度图)
|
||||
terrain_info = {
|
||||
'terrain': terrain,
|
||||
@ -273,18 +293,34 @@ class TerrainManager:
|
||||
|
||||
self.terrains.append(terrain_info)
|
||||
|
||||
print(f"✅ 为 {parent_item.text(0)} 创建平面地形: {terrain_name}")
|
||||
parent_name = parent_item.text(0) if parent_item else "root"
|
||||
print(f"✅ 为 {parent_name} 创建平面地形: {terrain_name}")
|
||||
|
||||
# 在Qt树形控件中添加对应节点
|
||||
qt_item = None
|
||||
if tree_widget and parent_item:
|
||||
qt_item = tree_widget.add_node_to_tree_widget(terrain_node, parent_item, "TERRAIN_NODE")
|
||||
|
||||
if qt_item:
|
||||
created_terrains.append((terrain_node, qt_item))
|
||||
else:
|
||||
created_terrains.append((terrain_node, None))
|
||||
print("⚠️ Qt树节点添加失败,但Panda3D对象已创建")
|
||||
print("⚠️ Qt树节点添加跳过或失败,但Panda3D对象已创建")
|
||||
|
||||
# 添加到场景管理器(ImGui环境使用)
|
||||
if hasattr(self.world, 'scene_manager') and self.world.scene_manager:
|
||||
if not hasattr(self.world.scene_manager, 'models'):
|
||||
self.world.scene_manager.models = []
|
||||
if terrain_node not in self.world.scene_manager.models:
|
||||
self.world.scene_manager.models.append(terrain_node)
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 为 {parent_item.text(0)} 创建平面地形: {str(e)}")
|
||||
parent_name = parent_item.text(0) if parent_item else "root"
|
||||
print(f"❌ 为 {parent_name} 创建平面地形失败: {str(e)}")
|
||||
return None
|
||||
|
||||
# 处理创建结果
|
||||
@ -501,14 +537,18 @@ class TerrainManager:
|
||||
tree_widget = self._get_tree_widget()
|
||||
if tree_widget:
|
||||
tree_widget.delete_items(tree_widget.selectedItems())
|
||||
# terrain_node.removeNode()
|
||||
#
|
||||
# # 从列表中移除
|
||||
# self.terrains.remove(terrain_info)
|
||||
else:
|
||||
terrain_node.removeNode()
|
||||
|
||||
# # 更新场景树
|
||||
# if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||||
# self.world.scene_manager.updateSceneTree()
|
||||
# 从列表中移除
|
||||
self.terrains.remove(terrain_info)
|
||||
|
||||
# 更新场景树 (如果是使用了ImGui界面或兼容界面)
|
||||
if hasattr(self.world, 'scene_manager'):
|
||||
if hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
if hasattr(self.world.scene_manager, 'models') and terrain_node in self.world.scene_manager.models:
|
||||
self.world.scene_manager.models.remove(terrain_node)
|
||||
|
||||
print(f"✓ 地形已删除: {terrain_info.get('name', 'Unknown')}")
|
||||
|
||||
|
||||
47
imgui.ini
47
imgui.ini
@ -24,26 +24,26 @@ Size=832,45
|
||||
Collapsed=0
|
||||
|
||||
[Window][工具栏]
|
||||
Pos=323,20
|
||||
Size=1870,32
|
||||
Pos=273,20
|
||||
Size=1276,32
|
||||
Collapsed=0
|
||||
DockId=0x0000000D,0
|
||||
|
||||
[Window][场景树]
|
||||
Pos=0,20
|
||||
Size=321,854
|
||||
Size=271,634
|
||||
Collapsed=0
|
||||
DockId=0x00000007,0
|
||||
|
||||
[Window][属性面板]
|
||||
Pos=2195,20
|
||||
Size=365,1331
|
||||
Pos=1551,20
|
||||
Size=369,989
|
||||
Collapsed=0
|
||||
DockId=0x00000003,0
|
||||
|
||||
[Window][控制台]
|
||||
Pos=0,876
|
||||
Size=321,475
|
||||
Pos=0,656
|
||||
Size=271,353
|
||||
Collapsed=0
|
||||
DockId=0x00000008,0
|
||||
|
||||
@ -60,7 +60,7 @@ Collapsed=0
|
||||
|
||||
[Window][WindowOverViewport_11111111]
|
||||
Pos=0,20
|
||||
Size=2560,1331
|
||||
Size=1920,989
|
||||
Collapsed=0
|
||||
|
||||
[Window][测试窗口1]
|
||||
@ -84,7 +84,7 @@ Size=400,300
|
||||
Collapsed=0
|
||||
|
||||
[Window][选择路径]
|
||||
Pos=980,425
|
||||
Pos=660,254
|
||||
Size=600,500
|
||||
Collapsed=0
|
||||
|
||||
@ -94,13 +94,13 @@ Size=500,400
|
||||
Collapsed=0
|
||||
|
||||
[Window][导入模型]
|
||||
Pos=980,425
|
||||
Pos=660,254
|
||||
Size=600,500
|
||||
Collapsed=0
|
||||
|
||||
[Window][资源管理器]
|
||||
Pos=323,977
|
||||
Size=1870,374
|
||||
Pos=273,817
|
||||
Size=1276,192
|
||||
Collapsed=0
|
||||
DockId=0x00000006,0
|
||||
|
||||
@ -150,8 +150,8 @@ Size=101,226
|
||||
Collapsed=0
|
||||
|
||||
[Window][LUI编辑器]
|
||||
Pos=1540,412
|
||||
Size=380,597
|
||||
Pos=1628,412
|
||||
Size=292,597
|
||||
Collapsed=0
|
||||
DockId=0x00000004,0
|
||||
|
||||
@ -195,18 +195,23 @@ Pos=474,130
|
||||
Size=120,384
|
||||
Collapsed=0
|
||||
|
||||
[Window][选择emission纹理文件##texture_dialog]
|
||||
Pos=660,304
|
||||
Size=600,400
|
||||
Collapsed=0
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=2560,1331 Split=X
|
||||
DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=1553,989 Split=X
|
||||
DockNode ID=0x00000009 Parent=0x00000001 SizeRef=321,989 Split=Y Selected=0xE0015051
|
||||
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1920,989 Split=X
|
||||
DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=1549,989 Split=X
|
||||
DockNode ID=0x00000009 Parent=0x00000001 SizeRef=271,989 Split=Y Selected=0xE0015051
|
||||
DockNode ID=0x00000007 Parent=0x00000009 SizeRef=271,634 Selected=0xE0015051
|
||||
DockNode ID=0x00000008 Parent=0x00000009 SizeRef=271,353 Selected=0x5428E753
|
||||
DockNode ID=0x0000000A Parent=0x00000001 SizeRef=1230,989 Split=Y
|
||||
DockNode ID=0x0000000A Parent=0x00000001 SizeRef=1276,989 Split=Y
|
||||
DockNode ID=0x0000000D Parent=0x0000000A SizeRef=1318,32 HiddenTabBar=1 Selected=0x43A39006
|
||||
DockNode ID=0x0000000E Parent=0x0000000A SizeRef=1318,955 Split=Y
|
||||
DockNode ID=0x00000005 Parent=0x0000000E SizeRef=1341,921 CentralNode=1
|
||||
DockNode ID=0x00000006 Parent=0x0000000E SizeRef=1341,374 Selected=0x3A2E05C3
|
||||
DockNode ID=0x00000002 Parent=0x08BD597D SizeRef=365,989 Split=Y Selected=0x3188AB8D
|
||||
DockNode ID=0x00000005 Parent=0x0000000E SizeRef=1341,761 CentralNode=1
|
||||
DockNode ID=0x00000006 Parent=0x0000000E SizeRef=1341,192 Selected=0x3A2E05C3
|
||||
DockNode ID=0x00000002 Parent=0x08BD597D SizeRef=369,989 Split=Y Selected=0x3188AB8D
|
||||
DockNode ID=0x00000003 Parent=0x00000002 SizeRef=351,390 Selected=0x5DB6FF37
|
||||
DockNode ID=0x00000004 Parent=0x00000002 SizeRef=351,597 Selected=0x1EB923B7
|
||||
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
场景管理器 - 负责场景和模型管理的核心功能
|
||||
处理模型导入、场景树构建、材质系统、碰撞设置等
|
||||
|
||||
@ -21,6 +21,21 @@ class AppActions:
|
||||
else:
|
||||
setattr(self.app, name, value)
|
||||
|
||||
def _resolve_cut_copy_node(self, node):
|
||||
"""Resolve selection to a stable scene root for copy/cut/paste."""
|
||||
if not node or node.isEmpty():
|
||||
return node
|
||||
|
||||
current = node
|
||||
while current and not current.isEmpty():
|
||||
if current.getName() == "render":
|
||||
break
|
||||
if current.hasTag("tree_item_type") or current.hasTag("is_model_root") or current.hasTag("is_scene_element"):
|
||||
return current
|
||||
current = current.getParent()
|
||||
|
||||
return node
|
||||
|
||||
|
||||
def _toggle_hot_reload(self):
|
||||
"""切换热重载状态"""
|
||||
@ -508,7 +523,7 @@ class AppActions:
|
||||
return
|
||||
|
||||
# 获取当前选中的节点
|
||||
selected_node = self.selection.selectedNode
|
||||
selected_node = self._resolve_cut_copy_node(self.selection.selectedNode)
|
||||
if not selected_node:
|
||||
self.add_warning_message("没有选中的节点")
|
||||
return
|
||||
@ -543,7 +558,7 @@ class AppActions:
|
||||
return
|
||||
|
||||
# 获取当前选中的节点
|
||||
selected_node = self.selection.selectedNode
|
||||
selected_node = self._resolve_cut_copy_node(self.selection.selectedNode)
|
||||
if not selected_node:
|
||||
self.add_warning_message("没有选中的节点")
|
||||
return
|
||||
@ -559,15 +574,16 @@ class AppActions:
|
||||
node_data = self.scene_manager.serializeNodeForCopy(selected_node)
|
||||
if node_data:
|
||||
self.clipboard = [node_data]
|
||||
# Cut uses serialized restore path after original deletion.
|
||||
self.clipboard_source_nodes = []
|
||||
# Cut preserves the source node references for cloning in paste
|
||||
self.clipboard_source_nodes = [selected_node]
|
||||
self.clipboard_mode = "cut"
|
||||
|
||||
# 删除原节点
|
||||
self._delete_node(selected_node)
|
||||
if self._delete_node(selected_node):
|
||||
self.selection.clearSelection()
|
||||
|
||||
self.add_success_message(f"已剪切节点: {node_name}")
|
||||
else:
|
||||
self.add_error_message(f"剪切失败,无法删除节点: {node_name}")
|
||||
else:
|
||||
self.add_error_message("节点序列化失败")
|
||||
else:
|
||||
@ -612,10 +628,14 @@ class AppActions:
|
||||
created = None
|
||||
|
||||
# Copy mode: prefer direct NodePath clone to preserve visual geometry.
|
||||
if self.clipboard_mode == "copy" and i < len(source_nodes):
|
||||
if self.clipboard_mode in ("copy", "cut") and i < len(source_nodes):
|
||||
source_node = source_nodes[i]
|
||||
if source_node and not source_node.isEmpty():
|
||||
try:
|
||||
if self.clipboard_mode == "cut":
|
||||
source_node.reparentTo(parent)
|
||||
created = source_node
|
||||
else:
|
||||
created = source_node.copyTo(parent)
|
||||
if hasattr(self.scene_manager, "_generateUniqueName"):
|
||||
unique_name = self.scene_manager._generateUniqueName(source_node.getName(), parent)
|
||||
|
||||
@ -386,7 +386,7 @@ class EditorPanels:
|
||||
# SSBO模式下,模型可能不在 scene_manager.models 中,补充显示 ssbo_editor.model
|
||||
ssbo_editor = getattr(self.app, "ssbo_editor", None)
|
||||
ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None
|
||||
if ssbo_model and not ssbo_model.isEmpty() and ssbo_model not in models:
|
||||
if ssbo_model and not ssbo_model.isEmpty() and ssbo_model.hasParent() and ssbo_model not in models:
|
||||
models.append(ssbo_model)
|
||||
|
||||
if models:
|
||||
|
||||
@ -790,47 +790,53 @@ class PropertyHelpers:
|
||||
def _select_texture_for_material(self, node, material, texture_type):
|
||||
"""为材质选择纹理"""
|
||||
try:
|
||||
# 设置当前纹理对话框状态
|
||||
self._current_texture_dialog = {
|
||||
'node': node,
|
||||
'material': material,
|
||||
'texture_type': texture_type
|
||||
}
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog
|
||||
|
||||
# 初始化路径
|
||||
if not hasattr(self, '_texture_dialog_path'):
|
||||
self._texture_dialog_path = '/home/hello/EG/Resources'
|
||||
# 推断初始目录
|
||||
initial_dir = getattr(self, "_texture_dialog_path", "")
|
||||
if not initial_dir or not os.path.exists(initial_dir):
|
||||
candidates = [
|
||||
os.path.join(os.getcwd(), "Resources", "textures"),
|
||||
os.path.join(os.getcwd(), "Resources"),
|
||||
os.getcwd(),
|
||||
]
|
||||
initial_dir = next((p for p in candidates if os.path.exists(p)), os.getcwd())
|
||||
|
||||
# 设置文件类型过滤
|
||||
self._texture_dialog_filter = "*.png"
|
||||
# 弹出系统文件选择窗口
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
try:
|
||||
root.attributes("-topmost", True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
selected_path = filedialog.askopenfilename(
|
||||
title=f"选择{texture_type}贴图",
|
||||
initialdir=initial_dir,
|
||||
filetypes=[
|
||||
("Image Files", "*.png;*.jpg;*.jpeg;*.bmp;*.tif;*.tiff;*.tga;*.dds;*.ktx;*.hdr;*.exr"),
|
||||
("All Files", "*.*"),
|
||||
],
|
||||
)
|
||||
root.destroy()
|
||||
|
||||
if selected_path and os.path.exists(selected_path):
|
||||
self._texture_dialog_path = os.path.dirname(selected_path)
|
||||
self._apply_texture_to_material(node, material, texture_type, selected_path)
|
||||
else:
|
||||
print(f"已取消选择{texture_type}贴图")
|
||||
|
||||
except Exception as e:
|
||||
print(f"选择纹理失败: {e}")
|
||||
|
||||
|
||||
def _apply_texture_to_material(self, node, material, texture_type, texture_path):
|
||||
"""应用纹理到材质"""
|
||||
try:
|
||||
from panda3d.core import TextureStage
|
||||
from direct.showbase import Loader
|
||||
|
||||
# 加载纹理
|
||||
loader = Loader.Loader(self)
|
||||
texture = loader.loadTexture(texture_path)
|
||||
|
||||
if not texture:
|
||||
print(f"无法加载纹理: {texture_path}")
|
||||
return
|
||||
|
||||
# 设置纹理属性
|
||||
texture.setMagfilter(texture.FTLinear)
|
||||
texture.setMinfilter(texture.FTLinearMipmapLinear)
|
||||
|
||||
# 纹理槽位映射
|
||||
texture_slots = {
|
||||
def _get_material_texture_slots(self):
|
||||
"""材质贴图类型到固定槽位映射(对应 p3d_TextureN)"""
|
||||
return {
|
||||
"diffuse": 0, # p3d_Texture0
|
||||
"ior": 2, # p3d_Texture2
|
||||
"normal": 1, # p3d_Texture1
|
||||
"ior": 2, # p3d_Texture2
|
||||
"roughness": 3, # p3d_Texture3
|
||||
"parallax": 4, # p3d_Texture4
|
||||
"metallic": 5, # p3d_Texture5
|
||||
@ -841,17 +847,356 @@ class PropertyHelpers:
|
||||
"gloss": 10 # p3d_Texture10
|
||||
}
|
||||
|
||||
slot = texture_slots.get(texture_type, 0)
|
||||
|
||||
# 创建纹理阶段
|
||||
texture_stage = TextureStage(f"{texture_type}_map")
|
||||
def _get_material_stage_mode_map(self):
|
||||
"""不同贴图类型对应的 TextureStage 模式"""
|
||||
from panda3d.core import TextureStage
|
||||
return {
|
||||
"diffuse": TextureStage.MModulate,
|
||||
"normal": TextureStage.MNormal,
|
||||
"roughness": TextureStage.MGloss,
|
||||
"metallic": TextureStage.MSelector,
|
||||
"emission": TextureStage.MGlow,
|
||||
"ao": TextureStage.MSelector,
|
||||
"alpha": TextureStage.MBlend,
|
||||
"parallax": TextureStage.MHeight,
|
||||
"detail": TextureStage.MModulate,
|
||||
"gloss": TextureStage.MGloss,
|
||||
"ior": TextureStage.MSelector,
|
||||
}
|
||||
|
||||
|
||||
def _find_texture_stage_by_name(self, node, stage_name):
|
||||
"""按名称查找节点上的纹理阶段"""
|
||||
texture_stages = node.findAllTextureStages()
|
||||
for i in range(texture_stages.getNumTextureStages()):
|
||||
stage = texture_stages.getTextureStage(i)
|
||||
if stage and stage.getName() == stage_name:
|
||||
return stage
|
||||
return None
|
||||
|
||||
|
||||
def _get_neutral_texture(self, texture_type):
|
||||
"""获取指定贴图类型的中性占位纹理(1x1)"""
|
||||
try:
|
||||
from panda3d.core import Texture
|
||||
|
||||
if not hasattr(self, "_neutral_texture_cache"):
|
||||
self._neutral_texture_cache = {}
|
||||
if texture_type in self._neutral_texture_cache:
|
||||
return self._neutral_texture_cache[texture_type]
|
||||
|
||||
# 各类型中性值,确保在未显式设置该通道时不影响结果
|
||||
neutral_rgba = {
|
||||
"diffuse": (255, 255, 255, 255), # 白色,不改变底色
|
||||
"normal": (128, 128, 255, 255), # 平面法线
|
||||
"ior": (0, 0, 0, 255), # blend_ior 中的“无附加影响”值
|
||||
"roughness": (255, 255, 255, 255), # 1.0
|
||||
"parallax": (0, 0, 0, 255), # 0 高度
|
||||
"metallic": (255, 255, 255, 255), # 1.0,保持乘法中性
|
||||
"emission": (0, 0, 0, 255), # 无自发光
|
||||
"ao": (255, 255, 255, 255), # 1.0,不衰减
|
||||
"alpha": (255, 255, 255, 255), # 完全不透明
|
||||
"detail": (255, 255, 255, 255), # 不改变细节叠加
|
||||
"gloss": (0, 0, 0, 255), # 0 光泽(对 roughness 影响最小)
|
||||
}
|
||||
|
||||
rgba = neutral_rgba.get(texture_type, (255, 255, 255, 255))
|
||||
tex = Texture(f"__neutral_{texture_type}")
|
||||
tex.setup2dTexture(1, 1, Texture.T_unsigned_byte, Texture.F_rgba8)
|
||||
tex.setRamImage(bytes(rgba))
|
||||
tex.setMagfilter(Texture.FTNearest)
|
||||
tex.setMinfilter(Texture.FTNearest)
|
||||
|
||||
self._neutral_texture_cache[texture_type] = tex
|
||||
return tex
|
||||
except Exception as e:
|
||||
print(f"创建中性纹理失败({texture_type}): {e}")
|
||||
return None
|
||||
|
||||
|
||||
def _ensure_texture_slot_alignment(self, node, target_slot, texture_slots, stage_mode_map):
|
||||
"""补齐低位槽的占位纹理,确保 p3d_TextureN 与固定槽位一致"""
|
||||
try:
|
||||
from panda3d.core import TextureStage
|
||||
except Exception:
|
||||
return
|
||||
|
||||
slot_to_type = {slot: tex_type for tex_type, slot in texture_slots.items()}
|
||||
for required_slot in range(target_slot):
|
||||
required_type = slot_to_type.get(required_slot)
|
||||
if not required_type:
|
||||
continue
|
||||
|
||||
stage_name = f"{required_type}_map"
|
||||
if self._find_texture_stage_by_name(node, stage_name):
|
||||
continue
|
||||
|
||||
neutral_texture = self._get_neutral_texture(required_type)
|
||||
if not neutral_texture:
|
||||
continue
|
||||
|
||||
stage = TextureStage(stage_name)
|
||||
stage.setSort(required_slot)
|
||||
stage.setMode(stage_mode_map.get(required_type, TextureStage.MSelector))
|
||||
node.setTexture(stage, neutral_texture, 1)
|
||||
|
||||
|
||||
def _ensure_metallic_texture_effect(self, node):
|
||||
"""启用支持 p3d_Texture5 的 effect,确保金属性贴图生效"""
|
||||
try:
|
||||
if not node or node.isEmpty():
|
||||
return
|
||||
|
||||
render_pipeline = getattr(self, "render_pipeline", None)
|
||||
if not render_pipeline:
|
||||
return
|
||||
|
||||
# 避免重复设置 effect
|
||||
if node.hasTag("material_effect_metallic_enabled"):
|
||||
return
|
||||
|
||||
render_pipeline.set_effect(
|
||||
node,
|
||||
"effects/pbr_with_metallic.yaml",
|
||||
{
|
||||
"normal_mapping": True,
|
||||
"render_gbuffer": True,
|
||||
"alpha_testing": False,
|
||||
"parallax_mapping": False,
|
||||
"render_shadow": True,
|
||||
"render_envmap": True
|
||||
},
|
||||
60
|
||||
)
|
||||
node.setTag("material_effect_metallic_enabled", "1")
|
||||
except Exception as e:
|
||||
print(f"启用金属性贴图 effect 失败: {e}")
|
||||
|
||||
|
||||
def _ensure_default_texture_effect(self, node, enable_parallax=False):
|
||||
"""启用默认贴图 effect(主要用于法线/粗糙度等通道)"""
|
||||
try:
|
||||
if not node or node.isEmpty():
|
||||
return
|
||||
|
||||
render_pipeline = getattr(self, "render_pipeline", None)
|
||||
if not render_pipeline:
|
||||
return
|
||||
|
||||
# 已启用金属性增强 effect 时,不覆盖它
|
||||
if node.hasTag("material_effect_metallic_enabled"):
|
||||
return
|
||||
|
||||
parallax_enabled = enable_parallax or node.hasTag("material_effect_parallax_enabled")
|
||||
if parallax_enabled:
|
||||
node.setTag("material_effect_parallax_enabled", "1")
|
||||
|
||||
# 为了避免因未知默认值导致 normal mapping 关闭,这里显式设置
|
||||
render_pipeline.set_effect(
|
||||
node,
|
||||
"effects/default.yaml",
|
||||
{
|
||||
"normal_mapping": True,
|
||||
"render_gbuffer": True,
|
||||
"alpha_testing": False,
|
||||
"parallax_mapping": parallax_enabled,
|
||||
"render_shadow": True,
|
||||
"render_envmap": True
|
||||
},
|
||||
55
|
||||
)
|
||||
node.setTag("material_effect_default_texture_enabled", "1")
|
||||
except Exception as e:
|
||||
print(f"启用默认贴图 effect 失败: {e}")
|
||||
|
||||
|
||||
def _set_material_scalar_property(self, material, prop_name, value):
|
||||
"""安全设置材质标量属性(兼容 snake_case / CamelCase)"""
|
||||
try:
|
||||
if not material:
|
||||
return False
|
||||
|
||||
setter_map = {
|
||||
"roughness": ("set_roughness", "setRoughness"),
|
||||
"metallic": ("set_metallic", "setMetallic"),
|
||||
"ior": ("set_refractive_index", "setRefractiveIndex"),
|
||||
}
|
||||
setters = setter_map.get(prop_name, ())
|
||||
for setter_name in setters:
|
||||
if hasattr(material, setter_name):
|
||||
getattr(material, setter_name)(value)
|
||||
return True
|
||||
|
||||
if hasattr(material, prop_name):
|
||||
setattr(material, prop_name, value)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"设置材质属性失败({prop_name}={value}): {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _get_material_emission(self, material):
|
||||
"""安全获取材质发光向量"""
|
||||
try:
|
||||
if not material:
|
||||
return None
|
||||
if hasattr(material, "emission") and material.emission is not None:
|
||||
return material.emission
|
||||
if hasattr(material, "get_emission"):
|
||||
return material.get_emission()
|
||||
if hasattr(material, "getEmission"):
|
||||
return material.getEmission()
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def _set_material_emission(self, material, emission_vec):
|
||||
"""安全设置材质发光向量"""
|
||||
try:
|
||||
if not material:
|
||||
return False
|
||||
if hasattr(material, "set_emission"):
|
||||
material.set_emission(emission_vec)
|
||||
return True
|
||||
if hasattr(material, "setEmission"):
|
||||
material.setEmission(emission_vec)
|
||||
return True
|
||||
if hasattr(material, "emission"):
|
||||
material.emission = emission_vec
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"设置材质发光参数失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _configure_material_for_texture_map(self, material, texture_type):
|
||||
"""贴图驱动参数修正:避免必须手动把数值调到 1.0 才生效"""
|
||||
try:
|
||||
from panda3d.core import Vec4
|
||||
|
||||
if not material:
|
||||
return
|
||||
|
||||
if texture_type == "roughness":
|
||||
self._set_material_scalar_property(material, "roughness", 1.0)
|
||||
elif texture_type == "metallic":
|
||||
# 默认 metallic=0 会把金属贴图乘没,设置为 1 让贴图直接驱动
|
||||
self._set_material_scalar_property(material, "metallic", 1.0)
|
||||
elif texture_type == "normal":
|
||||
# RenderPipeline 中 normalfactor 映射到 emission.y
|
||||
emission = self._get_material_emission(material)
|
||||
if emission is None:
|
||||
emission = Vec4(0.0, 0.0, 0.0, 1.0)
|
||||
|
||||
new_emission = Vec4(
|
||||
float(getattr(emission, "x", 0.0)),
|
||||
1.0,
|
||||
float(getattr(emission, "z", 0.0)),
|
||||
float(getattr(emission, "w", 1.0)),
|
||||
)
|
||||
self._set_material_emission(material, new_emission)
|
||||
except Exception as e:
|
||||
print(f"修正贴图驱动参数失败({texture_type}): {e}")
|
||||
|
||||
|
||||
def _apply_texture_to_material(self, node, material, texture_type, texture_path):
|
||||
"""应用纹理到材质"""
|
||||
try:
|
||||
import os
|
||||
from panda3d.core import TextureStage, Filename
|
||||
|
||||
# 加载纹理(优先使用应用的 loader,避免创建临时 Loader 导致状态不一致)
|
||||
loader = getattr(self, "loader", None)
|
||||
if not loader:
|
||||
print("无法加载纹理: loader 不可用")
|
||||
return
|
||||
|
||||
# 关键修复:把系统路径统一转换成 Panda3D 可识别格式(避免 D:/ 与 /d/ 冲突)
|
||||
normalized_path = texture_path
|
||||
if texture_path:
|
||||
try:
|
||||
from scene import util
|
||||
normalized_path = util.normalize_model_path(texture_path)
|
||||
except Exception:
|
||||
# 在 scene 模块初始化阶段可能出现循环导入,回退到 Panda3D 标准路径
|
||||
normalized_path = Filename.from_os_specific(texture_path).get_fullpath()
|
||||
panda_filename = Filename.from_os_specific(texture_path) if texture_path else None
|
||||
|
||||
texture = None
|
||||
candidate_paths = []
|
||||
if normalized_path:
|
||||
candidate_paths.append(normalized_path)
|
||||
if panda_filename:
|
||||
candidate_paths.append(panda_filename)
|
||||
candidate_paths.append(panda_filename.get_fullpath())
|
||||
if texture_path:
|
||||
candidate_paths.append(texture_path)
|
||||
|
||||
for path_candidate in candidate_paths:
|
||||
if not path_candidate:
|
||||
continue
|
||||
try:
|
||||
texture = loader.loadTexture(path_candidate)
|
||||
if texture:
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if not texture:
|
||||
print(f"无法加载纹理: {texture_path} (normalized={normalized_path})")
|
||||
return
|
||||
|
||||
# 设置纹理属性
|
||||
texture.setMagfilter(texture.FTLinear)
|
||||
texture.setMinfilter(texture.FTLinearMipmapLinear)
|
||||
|
||||
texture_type = (texture_type or "").strip().lower()
|
||||
texture_slots = self._get_material_texture_slots()
|
||||
if texture_type not in texture_slots:
|
||||
print(f"未知纹理类型: {texture_type}")
|
||||
return
|
||||
|
||||
slot = texture_slots[texture_type]
|
||||
|
||||
# 纹理阶段名保持稳定,便于重复设置时精确替换
|
||||
stage_name = f"{texture_type}_map"
|
||||
|
||||
# 修正材质参数,确保贴图不需要手工把滑块调到 1.0 才可见
|
||||
self._configure_material_for_texture_map(material, texture_type)
|
||||
|
||||
# 清理同类型旧 stage,避免叠加造成覆盖/污染
|
||||
old_stage = self._find_texture_stage_by_name(node, stage_name)
|
||||
if old_stage:
|
||||
node.clearTexture(old_stage)
|
||||
|
||||
# 关键修复:补齐低位槽,避免 p3d_TextureN 因“缺槽”而错位
|
||||
stage_mode_map = self._get_material_stage_mode_map()
|
||||
self._ensure_texture_slot_alignment(node, slot, texture_slots, stage_mode_map)
|
||||
|
||||
texture_stage = TextureStage(stage_name)
|
||||
texture_stage.setSort(slot)
|
||||
texture_stage.setMode(TextureStage.MModulate)
|
||||
texture_stage.setMode(stage_mode_map.get(texture_type, TextureStage.MSelector))
|
||||
|
||||
# 应用纹理到节点
|
||||
node.setTexture(texture_stage, texture)
|
||||
# 应用 stage(用于可视状态与序列化)
|
||||
node.setTexture(texture_stage, texture, 1)
|
||||
|
||||
print(f"已应用{texture_type}纹理: {texture_path}")
|
||||
# 同时写入 shader 输入,进一步保证固定槽位可直接读取
|
||||
node.setShaderInput(f"p3d_Texture{slot}", texture)
|
||||
|
||||
# 金属性贴图需要额外 effect 才会被默认管线采样
|
||||
if texture_type == "metallic":
|
||||
self._ensure_metallic_texture_effect(node)
|
||||
else:
|
||||
# 法线贴图依赖 normal_mapping 宏,显式开启默认 effect
|
||||
self._ensure_default_texture_effect(node, enable_parallax=(texture_type == "parallax"))
|
||||
|
||||
# 记录路径,便于 UI 展示和后续恢复
|
||||
if texture_path:
|
||||
node.setTag(f"material_texture_{texture_type}", os.path.normpath(normalized_path or texture_path))
|
||||
|
||||
print(f"已应用{texture_type}纹理到槽位 p3d_Texture{slot}: {texture_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"应用纹理失败: {e}")
|
||||
@ -862,7 +1207,24 @@ class PropertyHelpers:
|
||||
try:
|
||||
# 清除所有纹理阶段
|
||||
node.clearTexture()
|
||||
node.clearTexture()
|
||||
|
||||
# 清理额外 shader input 与记录标签
|
||||
texture_slots = self._get_material_texture_slots()
|
||||
for texture_type, slot in texture_slots.items():
|
||||
try:
|
||||
node.clearShaderInput(f"p3d_Texture{slot}")
|
||||
except Exception:
|
||||
pass
|
||||
tag_name = f"material_texture_{texture_type}"
|
||||
if node.hasTag(tag_name):
|
||||
node.clearTag(tag_name)
|
||||
if node.hasTag("material_effect_metallic_enabled"):
|
||||
node.clearTag("material_effect_metallic_enabled")
|
||||
if node.hasTag("material_effect_default_texture_enabled"):
|
||||
node.clearTag("material_effect_default_texture_enabled")
|
||||
if node.hasTag("material_effect_parallax_enabled"):
|
||||
node.clearTag("material_effect_parallax_enabled")
|
||||
|
||||
print("已清除所有纹理")
|
||||
except Exception as e:
|
||||
print(f"清除纹理失败: {e}")
|
||||
@ -871,22 +1233,38 @@ class PropertyHelpers:
|
||||
def _display_current_textures(self, node, material):
|
||||
"""显示当前纹理信息"""
|
||||
try:
|
||||
from panda3d.core import TextureStage
|
||||
|
||||
# 获取所有纹理阶段
|
||||
texture_stages = node.findAllTextureStages()
|
||||
|
||||
if not texture_stages:
|
||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "当前无纹理")
|
||||
return
|
||||
has_any = False
|
||||
|
||||
if texture_stages:
|
||||
imgui.text("当前纹理:")
|
||||
for stage in texture_stages:
|
||||
for i in range(texture_stages.getNumTextureStages()):
|
||||
stage = texture_stages.getTextureStage(i)
|
||||
texture = node.getTexture(stage)
|
||||
if texture:
|
||||
texture_name = texture.getName() or "未命名"
|
||||
stage_name = stage.getName() or "未命名"
|
||||
# 隐藏未被用户显式设置的占位纹理,避免面板误导
|
||||
if stage_name.endswith("_map"):
|
||||
tex_type = stage_name[:-4]
|
||||
if tex_type and not node.hasTag(f"material_texture_{tex_type}"):
|
||||
continue
|
||||
|
||||
texture_name = texture.getName() or "未命名"
|
||||
imgui.text(f" {stage_name}: {texture_name}")
|
||||
has_any = True
|
||||
|
||||
# 显示属性面板记录的贴图路径(包含非漫反射通道)
|
||||
tracked_types = ["diffuse", "normal", "roughness", "metallic", "emission", "ao", "alpha", "parallax", "detail", "gloss", "ior"]
|
||||
for texture_type in tracked_types:
|
||||
tag_name = f"material_texture_{texture_type}"
|
||||
if node.hasTag(tag_name):
|
||||
imgui.text(f" {texture_type}: {node.getTag(tag_name)}")
|
||||
has_any = True
|
||||
|
||||
if not has_any:
|
||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "当前无纹理")
|
||||
except Exception as e:
|
||||
print(f"显示纹理信息失败: {e}")
|
||||
|
||||
@ -943,148 +1321,9 @@ class PropertyHelpers:
|
||||
|
||||
|
||||
def _draw_texture_file_dialog(self):
|
||||
"""绘制纹理文件选择对话框"""
|
||||
if not hasattr(self, '_current_texture_dialog') or not self._current_texture_dialog:
|
||||
"""系统文件对话框模式下无需绘制 ImGui 贴图对话框"""
|
||||
return
|
||||
|
||||
try:
|
||||
dialog_data = self._current_texture_dialog
|
||||
node = dialog_data['node']
|
||||
material = dialog_data['material']
|
||||
texture_type = dialog_data['texture_type']
|
||||
|
||||
# 设置对话框标志
|
||||
flags = (imgui.WindowFlags_.no_resize |
|
||||
imgui.WindowFlags_.no_collapse |
|
||||
imgui.WindowFlags_.modal)
|
||||
|
||||
# 获取屏幕尺寸,居中显示对话框
|
||||
display_size = imgui.get_io().display_size
|
||||
dialog_width = 600
|
||||
dialog_height = 400
|
||||
imgui.set_next_window_size((dialog_width, dialog_height))
|
||||
imgui.set_next_window_pos(
|
||||
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
|
||||
)
|
||||
|
||||
# 显示文件选择对话框
|
||||
opened, window_open = imgui.begin(f"选择{texture_type}纹理文件##texture_dialog", True, flags)
|
||||
if not window_open:
|
||||
self._current_texture_dialog = None
|
||||
imgui.end()
|
||||
return
|
||||
|
||||
imgui.text(f"选择{texture_type}纹理文件")
|
||||
imgui.separator()
|
||||
|
||||
# 当前路径显示
|
||||
current_path = getattr(self, '_texture_dialog_path', '/home/hello/EG/Resources')
|
||||
imgui.text(f"当前路径: {current_path}")
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# 文件类型过滤
|
||||
imgui.text("支持的纹理格式:")
|
||||
file_types = ["*.png", "*.jpg", "*.jpeg", "*.bmp", "*.tga", "*.dds"]
|
||||
|
||||
current_filter = getattr(self, '_texture_dialog_filter', "*.png")
|
||||
if imgui.begin_combo("文件类型##texture_filter", current_filter):
|
||||
for i, file_type in enumerate(file_types):
|
||||
if imgui.selectable(file_type, i == file_types.index(current_filter)):
|
||||
self._texture_dialog_filter = file_type
|
||||
imgui.end_combo()
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# 路径导航按钮
|
||||
if imgui.button("上级目录##up_dir"):
|
||||
current_path = os.path.dirname(current_path)
|
||||
self._texture_dialog_path = current_path
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("主目录##home_dir"):
|
||||
self._texture_dialog_path = '/home/hello/EG/Resources'
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("当前目录##current_dir"):
|
||||
self._texture_dialog_path = '/home/hello/EG/Resources'
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("纹理目录##textures_dir"):
|
||||
self._texture_dialog_path = '/home/hello/EG/Resources/textures'
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# 文件列表
|
||||
if imgui.begin_child("file_list##texture_files", (580, 200)):
|
||||
try:
|
||||
# 列出目录和文件
|
||||
items = []
|
||||
if os.path.exists(current_path):
|
||||
for item in os.listdir(current_path):
|
||||
item_path = os.path.join(current_path, item)
|
||||
if os.path.isdir(item_path):
|
||||
items.append(('dir', item, item_path))
|
||||
elif any(item.lower().endswith(ext[1:]) for ext in file_types):
|
||||
items.append(('file', item, item_path))
|
||||
|
||||
# 排序:目录在前,文件在后
|
||||
items.sort(key=lambda x: (x[0], x[1].lower()))
|
||||
|
||||
for item_type, item_name, item_path in items:
|
||||
if item_type == 'dir':
|
||||
if imgui.selectable(f"📁 {item_name}##dir_{item_name}", False)[0]:
|
||||
self._texture_dialog_path = item_path
|
||||
else:
|
||||
selected, _ = imgui.selectable(f"📄 {item_name}##file_{item_name}", False)
|
||||
if selected:
|
||||
# 应用选择的纹理
|
||||
self._apply_texture_to_material(node, material, texture_type, item_path)
|
||||
# 关闭对话框
|
||||
self._current_texture_dialog = None
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
imgui.text_colored((1.0, 0.5, 0.5, 1.0), f"读取目录失败: {e}")
|
||||
|
||||
imgui.end_child()
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# 路径输入框
|
||||
changed, new_path = imgui.input_text("文件路径##texture_path", current_path, 512)
|
||||
if changed:
|
||||
self._texture_dialog_path = new_path
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("确认路径##confirm_path"):
|
||||
if os.path.isfile(new_path):
|
||||
# 检查文件扩展名
|
||||
file_ext = os.path.splitext(new_path)[1].lower()
|
||||
if file_ext in [ext[1:] for ext in file_types]:
|
||||
self._apply_texture_to_material(node, material, texture_type, new_path)
|
||||
self._current_texture_dialog = None
|
||||
else:
|
||||
print(f"不支持的文件格式: {file_ext}")
|
||||
else:
|
||||
print("请选择有效的文件")
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# 按钮
|
||||
if imgui.button("取消##cancel_texture"):
|
||||
self._current_texture_dialog = None
|
||||
|
||||
imgui.end()
|
||||
|
||||
except Exception as e:
|
||||
print(f"绘制纹理对话框失败: {e}")
|
||||
# 确保在异常情况下也调用 imgui.end()
|
||||
try:
|
||||
imgui.end()
|
||||
except:
|
||||
pass
|
||||
|
||||
def start_transform_monitoring(self, node):
|
||||
"""开始变换监控"""
|
||||
if node and not node.isEmpty():
|
||||
|
||||
Loading…
Reference in New Issue
Block a user