模型导入成黑色问题修复。打开项目使用SSBO导入模型逻辑流畅运行。

This commit is contained in:
Hector 2026-02-28 17:39:04 +08:00
parent 036b68ef41
commit 37b3cc30dc
17 changed files with 955 additions and 6244 deletions

1
EG

@ -1 +0,0 @@
Subproject commit 69e2bda47e9713705ad5c45a08b6fc643a2b51f6

File diff suppressed because it is too large Load Diff

View File

@ -389,6 +389,12 @@ class ResourceManager:
shutil.copytree(src, dst)
else:
shutil.copy2(src, dst)
if src.suffix.lower() == '.fbx':
fbm_src = src.with_name(src.stem + '.fbm')
if fbm_src.exists() and fbm_src.is_dir():
fbm_dst = destination_root / fbm_src.name
if not fbm_dst.exists():
shutil.copytree(fbm_src, fbm_dst)
imported.append(dst)
except Exception as e:
errors.append(f"导入失败 {src}: {e}")

View File

@ -2198,6 +2198,40 @@ class SelectionSystem:
"""获取当前选中的节点"""
return self.selectedNode
def deleteSelectedNode(self):
"""兼容旧接口:删除当前选中节点。"""
node = self.selectedNode
if not node or node.isEmpty():
return False
try:
if node.getName() == "render":
return False
except Exception:
return False
self._deleting_node = True
try:
# 优先走应用层统一删除链路(命令系统/SSBO清理/消息等)。
if hasattr(self.world, "_delete_node") and callable(self.world._delete_node):
deleted = bool(self.world._delete_node(node))
else:
deleted = False
scene_manager = getattr(self.world, "scene_manager", None)
if scene_manager and hasattr(scene_manager, "models") and node in scene_manager.models:
scene_manager.models.remove(node)
try:
node.removeNode()
deleted = True
except Exception:
deleted = False
if deleted:
self.clearSelection()
return deleted
finally:
self._deleting_node = False
def hasSelection(self):
"""检查是否有选中的节点"""
return self.selectedNode is not None

View File

@ -48,8 +48,8 @@ Collapsed=0
DockId=0x00000008,0
[Window][脚本管理]
Pos=1540,20
Size=380,390
Pos=1653,20
Size=267,390
Collapsed=0
DockId=0x00000003,1
@ -84,7 +84,7 @@ Size=400,300
Collapsed=0
[Window][选择路径]
Pos=660,245
Pos=660,254
Size=600,500
Collapsed=0
@ -150,8 +150,8 @@ Size=101,226
Collapsed=0
[Window][LUI编辑器]
Pos=1628,412
Size=292,597
Pos=1653,412
Size=267,597
Collapsed=0
DockId=0x00000004,0

View File

@ -26,7 +26,6 @@ from core.Command_System import CommandManager
from core.terrain_manager import TerrainManager
from scene.scene_manager import SceneManager
from project.project_manager import ProjectManager
from core.InfoPanelManager import InfoPanelManager
from core.collision_manager import CollisionManager
from core.CustomMouseController import CustomMouseController
from core.resource_manager import ResourceManager
@ -78,6 +77,7 @@ class MyWorld(PanelDelegates, CoreWorld):
self.accept("F", self.onFocusKeyPressed) # 大写F
self.use_ssbo_mouse_picking = True
self.use_ssbo_scene_import = True
if not self.use_ssbo_mouse_picking:
self.accept("mouse1", self.onMouseClick)
# Keep release/move bindings even in SSBO mode so gizmo drag can work.
@ -117,8 +117,6 @@ class MyWorld(PanelDelegates, CoreWorld):
self.project_manager = ProjectManager(self)
# print(f"[DEBUG] 项目管理系统初始化完成")
self.info_panel_manager = InfoPanelManager(self)
self.command_manager = CommandManager()
# 初始化碰撞管理器

View File

@ -478,7 +478,6 @@ class ProjectManager:
def _copyScriptSystemToBuild(self,build_dir):
core_files = [
"script_system.py",
"InfoPanelManager.py",
"CustomMouseController.py"
]

View File

@ -74,8 +74,8 @@ class SceneManagerIOMixin:
gui_info["parent_name"] = parent_name
# 收集所有标签仅对NodePath类型的对象
if hasattr(gui_node, 'getTagNames'):
for tag in gui_node.getTagNames():
if hasattr(gui_node, 'getTagKeys'):
for tag in gui_node.getTagKeys():
gui_info["tags"][tag] = gui_node.getTag(tag)
elif hasattr(gui_node, 'getTags'): # 对于DirectGUI对象
# DirectGUI对象使用不同的方法存储标签
@ -219,22 +219,6 @@ class SceneManagerIOMixin:
all_nodes.extend(self.Spotlight)
all_nodes.extend(self.Pointlight)
# 添加GUI元素节点
gui_elements = []
if hasattr(self.world, 'gui_elements'):
# 过滤掉空的或重复的GUI元素
unique_gui_elements = []
seen_names = set()
for elem in self.world.gui_elements:
if elem and not elem.isEmpty():
if not elem.isEmpty() and elem.getName() not in seen_names:
unique_gui_elements.append(elem)
seen_names.add(elem.getName())
gui_elements = unique_gui_elements
print(f"保存时GUI元素列表=>>>>>>>>>>>>{self.world.gui_elements}")
all_nodes.extend(gui_elements)
# 创建用于保存GUI信息的JSON文件路径
gui_info_file = filename.replace('.bam', '_gui.json')
@ -321,15 +305,6 @@ class SceneManagerIOMixin:
node.setTag(f"python_tag_{tag_name}", str(tag_value))
except:
pass
elif node.hasTag("element_type") and node.getTag("element_type") == "info_panel":
# 保存信息面板特定信息
print(f"保存信息面板信息: {node.getName()}")
panel_id = node.getTag("panel_id") if node.hasTag("panel_id") else node.getName()
if hasattr(self.world, 'info_panel_manager'):
panel_data = self.world.info_panel_manager.serializePanelData(panel_id)
if panel_data:
import json
node.setTag("info_panel_data", json.dumps(panel_data, ensure_ascii=False))
# 保存模型动画信息
elif node.hasTag("is_model_root"):
@ -448,11 +423,7 @@ class SceneManagerIOMixin:
max_retries = 3
try:
print(f"\n=== 开始加载场景: {filename} (尝试 {retry_count + 1}/{max_retries + 1}) ===")
# print(f"[DEBUG] loadScene 被调用,参数: {filename}")
# print(f"[DEBUG] 当前场景中的模型数量: {len(self.models)}")
# print(f"[DEBUG] 当前场景中的灯光数量: {len(self.Spotlight) + len(self.Pointlight)}")
# print(f"[DEBUG] 当前GUI元素数量: {len(self.world.gui_elements) if hasattr(self.world, 'gui_elements') else 0}")
# 确保文件路径是规范化的
filename = os.path.normpath(filename)
# print(f"[DEBUG] 规范化后的文件路径: {filename}")
@ -553,15 +524,6 @@ class SceneManagerIOMixin:
if not light.isEmpty():
light.removeNode()
self.Pointlight.clear()
for gui in self.world.gui_elements:
if not gui.isEmpty():
gui.removeNode()
self.world.gui_elements.clear()
if hasattr(self.world,'info_panel_manager'):
self.world.info_panel_manager.removeAllPanels()
# 清理可能存在的辅助节点
self._cleanupAuxiliaryNodes()
@ -587,18 +549,55 @@ class SceneManagerIOMixin:
# 4. 使用更安全的加载方式
# print(f"[DEBUG] 尝试加载BAM文件...")
panda_filename = Filename.fromOsSpecific(filename)
# print(f"[DEBUG] Panda3D文件名: {panda_filename}")
# 设置加载选项
from panda3d.core import LoaderOptions
loader_options = LoaderOptions()
loader_options.setFlags(loader_options.LFNoCache) # 禁用缓存
scene = self.world.loader.loadModel(panda_filename, loader_options)
if not scene:
print("场景加载失败")
# 兼容中文路径:优先使用宽字符接口,再回退常规接口。
candidate_files = []
for ctor_name in ("fromOsSpecificW", "from_os_specific_w", "fromOsSpecific", "from_os_specific"):
ctor = getattr(Filename, ctor_name, None)
if not ctor:
continue
try:
fn = ctor(filename)
key = fn.get_fullpath()
if key not in [c.get_fullpath() for c in candidate_files]:
candidate_files.append(fn)
except Exception:
continue
if not candidate_files:
candidate_files = [Filename(filename)]
scene = None
last_error = None
for panda_filename in candidate_files:
try:
scene = self.world.loader.loadModel(panda_filename, loader_options)
if scene and not scene.isEmpty():
break
except Exception as e:
last_error = e
scene = None
# 极端回退把场景复制到ASCII路径再加载规避少数编码问题
if (not scene or scene.isEmpty()) and os.path.exists(filename):
try:
fallback_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "_scene_load_cache")
os.makedirs(fallback_dir, exist_ok=True)
fallback_file = os.path.join(fallback_dir, "scene_fallback.bam")
shutil.copy2(filename, fallback_file)
scene = self.world.loader.loadModel(Filename.fromOsSpecific(fallback_file), loader_options)
if scene and not scene.isEmpty():
print("[SceneLoad] Fallback loaded from ASCII cache path.")
except Exception as e:
last_error = e
if not scene or scene.isEmpty():
print(f"场景加载失败: {filename}")
if last_error:
print(f"[SceneLoad] 最后错误: {last_error}")
return False
# print(f"[DEBUG] BAM文件加载成功")
@ -640,6 +639,26 @@ class SceneManagerIOMixin:
#存储所有加载的节点,用于后续处理父子关系
loaded_nodes = {} #name->nodePath映射
use_ssbo_scene_import = bool(
getattr(self.world, "use_ssbo_mouse_picking", False) and
getattr(self.world, "use_ssbo_scene_import", False) and
getattr(self.world, "ssbo_editor", None) and
callable(getattr(self.world, "_import_model_for_runtime", None))
)
ssbo_scene_model = None
if use_ssbo_scene_import:
try:
print(f"[SSBO] 打开项目使用统一导入链路: {filename}")
ssbo_scene_model = self.world._import_model_for_runtime(filename)
if ssbo_scene_model and not ssbo_scene_model.isEmpty():
if ssbo_scene_model not in self.models:
self.models.append(ssbo_scene_model)
else:
print("[SSBO] 统一导入未返回有效模型,回退旧流程。")
use_ssbo_scene_import = False
except Exception as e:
print(f"[SSBO] 统一导入失败,回退旧流程: {e}")
use_ssbo_scene_import = False
# 遍历场景中的所有节点
def processNode(nodePath, depth=0):
@ -700,6 +719,7 @@ class SceneManagerIOMixin:
if is_scene_element or is_potential_gui:
print(f"{indent}找到场景元素节点: {nodePath.getName()}")
is_model_root = nodePath.hasTag("is_model_root")
# 如果是潜在的GUI元素但没有标签添加基本标签
if is_potential_gui and not (nodePath.hasTag("gui_type") or nodePath.hasTag("is_gui_element")):
@ -721,9 +741,7 @@ class SceneManagerIOMixin:
else:
nodePath.setTag("gui_type", "unknown")
# 清除现有材质状态
nodePath.clearMaterial()
nodePath.clearColor()
# 保留原始材质/颜色状态,避免在场景恢复阶段造成黑模。
# 恢复变换信息
def parseVec3(vec_str):
@ -769,7 +787,11 @@ class SceneManagerIOMixin:
else:
nodePath.hide()
if nodePath.hasTag("has_scripts") and nodePath.getTag("has_scripts") == "true":
if (
nodePath.hasTag("has_scripts")
and nodePath.getTag("has_scripts") == "true"
and not (use_ssbo_scene_import and is_model_root)
):
if hasattr(self.world,'script_manager') and self.world.script_manager:
try:
import json
@ -824,40 +846,41 @@ class SceneManagerIOMixin:
except:
return Vec4(1, 1, 1, 1)
# 创建并恢复材质
material = Material()
material_changed = False
if not is_model_root:
# 创建并恢复材质
material = Material()
material_changed = False
if nodePath.hasTag("material_ambient"):
material.setAmbient(parseColor(nodePath.getTag("material_ambient")))
material_changed = True
if nodePath.hasTag("material_ambient"):
material.setAmbient(parseColor(nodePath.getTag("material_ambient")))
material_changed = True
if nodePath.hasTag("material_diffuse"):
material.setDiffuse(parseColor(nodePath.getTag("material_diffuse")))
material_changed = True
if nodePath.hasTag("material_diffuse"):
material.setDiffuse(parseColor(nodePath.getTag("material_diffuse")))
material_changed = True
if nodePath.hasTag("material_specular"):
material.setSpecular(parseColor(nodePath.getTag("material_specular")))
material_changed = True
if nodePath.hasTag("material_specular"):
material.setSpecular(parseColor(nodePath.getTag("material_specular")))
material_changed = True
if nodePath.hasTag("material_emission"):
material.setEmission(parseColor(nodePath.getTag("material_emission")))
material_changed = True
if nodePath.hasTag("material_emission"):
material.setEmission(parseColor(nodePath.getTag("material_emission")))
material_changed = True
if nodePath.hasTag("material_shininess"):
material.setShininess(float(nodePath.getTag("material_shininess")))
material_changed = True
if nodePath.hasTag("material_shininess"):
material.setShininess(float(nodePath.getTag("material_shininess")))
material_changed = True
if nodePath.hasTag("material_basecolor"):
material.setBaseColor(parseColor(nodePath.getTag("material_basecolor")))
material_changed = True
if nodePath.hasTag("material_basecolor"):
material.setBaseColor(parseColor(nodePath.getTag("material_basecolor")))
material_changed = True
if material_changed:
nodePath.setMaterial(material)
if material_changed:
nodePath.setMaterial(material)
# 恢复颜色属性
if nodePath.hasTag("color"):
nodePath.setColor(parseColor(nodePath.getTag("color")))
# 恢复颜色属性
if nodePath.hasTag("color"):
nodePath.setColor(parseColor(nodePath.getTag("color")))
# 处理特定类型的节点
if nodePath.hasTag("light_type"):
@ -893,32 +916,47 @@ class SceneManagerIOMixin:
# 这里我们先保持原挂载关系
pass
else:
if use_ssbo_scene_import and is_model_root:
# SSBO 模式下模型节点由新导入链路接管,这里不直接挂载。
pass
# 其他节点确保挂载到render下
if nodePath.getParent() != self.world.render and not nodePath.getName() in ["render",
"aspect2d",
"render2d"]:
elif nodePath.getParent() != self.world.render and not nodePath.getName() in ["render",
"aspect2d",
"render2d"]:
nodePath.wrtReparentTo(self.world.render)
# 为模型节点设置碰撞检测
if nodePath.hasTag("is_model_root"):
if is_model_root:
print(f"J{indent}处理模型节点{nodePath.getName()}")
#self._validateAndFixAllTransforms(nodePath)
self._fixModelStructure(nodePath)
# 恢复模型动画信息
self._restoreModelAnimationInfo(nodePath)
# 检测并处理模型动画类似property_panel.py中的逻辑
self._processModelAnimations(nodePath)
# if self.world.property_panel._hasCollision(nodePath):
# print(f"{indent}模型{nodePath.getName()}已有碰撞体,跳过碰撞体设置")
# else:
# print(f"{indent}为模型{nodePath.getName()}设置碰撞检测")
# self.setupCollision(nodePath)
self.models.append(nodePath)
if use_ssbo_scene_import:
# SSBO 模式下整个 scene.bam 已通过统一导入链路载入,
# 这里跳过逐模型旧导入逻辑,避免与菜单导入路径不一致。
pass
else:
#self._validateAndFixAllTransforms(nodePath)
self._fixModelStructure(nodePath)
self._restoreModelAnimationInfo(nodePath)
self._processModelAnimations(nodePath)
try:
ssbo_editor = getattr(self.world, "ssbo_editor", None)
repair_fn = getattr(ssbo_editor, "_repair_missing_textures", None) if ssbo_editor else None
if callable(repair_fn):
model_path = ""
for tag_name in ("model_path", "saved_model_path", "original_path", "file"):
if nodePath.hasTag(tag_name):
candidate = nodePath.getTag(tag_name).strip()
if candidate:
model_path = candidate
break
repair_fn(nodePath, model_path or filename)
except Exception as e:
print(f"[SceneLoad] 贴图修复失败: {e}")
# if self.world.property_panel._hasCollision(nodePath):
# print(f"{indent}模型{nodePath.getName()}已有碰撞体,跳过碰撞体设置")
# else:
# print(f"{indent}为模型{nodePath.getName()}设置碰撞检测")
# self.setupCollision(nodePath)
self.models.append(nodePath)
# 递归处理子节点
for child in nodePath.getChildren():
@ -927,6 +965,8 @@ class SceneManagerIOMixin:
print("\n开始处理场景节点...")
processNode(scene)
# SSBO 模式下模型已在前面统一导入;若失败已自动回退旧流程。
#处理父子关系 - 在所有节点加载完成后设置正确的父子关系
print("\n开始重建父子关系...")
self._rebuildParentChildRelationships(loaded_nodes)
@ -964,13 +1004,6 @@ class SceneManagerIOMixin:
#self.updateSceneTree()
#self._get_tree_widget().create_model_items(scene)
print(f"加载完成GUI元素数量: {len(self.world.gui_elements)}")
if len(self.world.gui_elements) > 0:
print("GUI元素列表:")
for i, elem in enumerate(self.world.gui_elements):
print(
f" {i + 1}. {elem.getName()} (类型: {elem.getTag('gui_type') if elem.hasTag('gui_type') else 'unknown'})")
print("=== 场景加载完成 ===\n")
return True

View File

@ -61,11 +61,8 @@ class SceneManagerModelMixin:
model_name = "imported_model"
model.setName(model_name)
# 设置默认颜色以避免get_color警告
try:
model.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰
except:
pass # 如果设置颜色失败,继续执行
# 移除统一设置颜色的代码因为它可能覆盖PBR纹理并导致模型在RenderPipeline中渲染异常纯黑
# # model.setColor(0.8, 0.8, 0.8, 1.0) # 移除以防覆盖PBR纹理
# 将模型添加到场景
model.reparentTo(self.world.render)
@ -140,7 +137,7 @@ class SceneManagerModelMixin:
# 创建并设置基础材质
#print("\n=== 开始设置材质 ===")
#self._applyMaterialsToModel(model)
self._fixBlackMaterials(model)
# 设置碰撞检测(重要!用于选择功能)
print("\n=== 设置碰撞检测 ===")
@ -467,6 +464,71 @@ class SceneManagerModelMixin:
print(f"应用材质时出错: {e}")
print("=== 材质设置完成 ===\n")
def _fixBlackMaterials(self, model):
# 修复模型中全黑的材质问题,同时为缺失材质的几何体添加默认材质,保留原有纹理
try:
from panda3d.core import MaterialAttrib, Material, GeomNode
for geom_path in model.findAllMatches('**/+GeomNode'):
geom_node = geom_path.node()
# 级联节点状态
node_state = geom_path.getState()
if node_state.hasAttrib(MaterialAttrib.getClassType()):
mat_attrib = node_state.getAttrib(MaterialAttrib.getClassType())
mat = mat_attrib.getMaterial()
if mat:
is_black = False
if mat.hasBaseColor():
c = mat.getBaseColor()
if c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05:
is_black = True
elif mat.hasDiffuse():
c = mat.getDiffuse()
if c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05:
is_black = True
if is_black or not (mat.hasBaseColor() or mat.hasDiffuse()):
new_mat = Material(mat)
new_mat.setBaseColor((0.8, 0.8, 0.8, 1.0))
new_mat.setDiffuse((0.8, 0.8, 0.8, 1.0))
geom_path.setState(node_state.setAttrib(MaterialAttrib.make(new_mat)))
# 几何体状态
for i in range(geom_node.getNumGeoms()):
geom_state = geom_node.getGeomState(i)
if geom_state.hasAttrib(MaterialAttrib.getClassType()):
mat_attrib = geom_state.getAttrib(MaterialAttrib.getClassType())
mat = mat_attrib.getMaterial()
if mat:
is_black = False
if mat.hasBaseColor():
c = mat.getBaseColor()
if c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05:
is_black = True
elif mat.hasDiffuse():
c = mat.getDiffuse()
if c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05:
is_black = True
if is_black or not (mat.hasBaseColor() or mat.hasDiffuse()):
new_mat = Material(mat)
new_mat.setBaseColor((0.8, 0.8, 0.8, 1.0))
new_mat.setDiffuse((0.8, 0.8, 0.8, 1.0))
geom_node.setGeomState(i, geom_state.setAttrib(MaterialAttrib.make(new_mat)))
else:
new_mat = Material()
new_mat.setBaseColor((0.8, 0.8, 0.8, 1.0))
new_mat.setDiffuse((0.8, 0.8, 0.8, 1.0))
new_mat.setSpecular((0.2, 0.2, 0.2, 1.0))
new_mat.setRoughness(0.8)
geom_node.setGeomState(i, geom_state.addAttrib(MaterialAttrib.make(new_mat)))
model.clearColor()
except Exception as e:
print(f'修复黑色模型材质时出错: {e}')
def _adjustModelToGround(self, model):
"""智能调整模型到地面,但保持原有缩放结构"""
try:
@ -917,15 +979,27 @@ class SceneManagerModelMixin:
from direct.actor.Actor import Actor
from panda3d.core import Filename
candidate_paths = [model_path]
candidate_paths.append(Filename.from_os_specific(model_path).get_fullpath())
candidate_paths = []
# Always prefer normalized Panda paths; avoid raw Windows path fallback,
# which triggers noisy loader(error) logs for some absolute/Unicode paths.
try:
normalized = util.normalize_model_path(model_path)
if normalized:
if normalized and normalized != model_path:
candidate_paths.append(normalized)
except Exception:
pass
for ctor_name in ("fromOsSpecificW", "from_os_specific_w", "fromOsSpecific", "from_os_specific"):
ctor = getattr(Filename, ctor_name, None)
if not ctor:
continue
try:
panda_path = ctor(model_path).get_fullpath()
if panda_path:
candidate_paths.append(panda_path)
except Exception:
continue
seen = set()
unique_paths = []
for p in candidate_paths:

View File

@ -56,16 +56,29 @@ class CrossPlatformPathHandler:
exists = os.path.exists(filepath)
return exists
def _panda3d_normalize(self, filepath):
"""使用Panda3D标准化路径"""
try:
panda_filename = Filename.from_os_specific(filepath)
normalized_path = panda_filename.get_fullpath()
print(f"✓ Panda3D标准化: {normalized_path}")
return normalized_path
except Exception as e:
print(f"⚠️ Panda3D标准化失败: {e}")
return filepath
def _panda3d_normalize(self, filepath):
"""使用Panda3D标准化路径"""
for ctor_name in ("fromOsSpecificW", "from_os_specific_w", "fromOsSpecific", "from_os_specific"):
ctor = getattr(Filename, ctor_name, None)
if not ctor:
continue
try:
panda_filename = ctor(filepath)
normalized_path = panda_filename.get_fullpath()
if normalized_path:
print(f"✓ Panda3D标准化: {normalized_path}")
return normalized_path
except Exception:
continue
try:
panda_filename = Filename(filepath)
normalized_path = panda_filename.get_fullpath()
if normalized_path:
print(f"✓ Panda3D标准化: {normalized_path}")
return normalized_path
except Exception as e:
print(f"⚠️ Panda3D标准化失败: {e}")
return filepath
def _attempt_path_fixes(self, filepath):
"""尝试各种路径修复方法"""
@ -293,4 +306,4 @@ def normalize_model_path(filepath):
def suggest_file_placement(filename):
"""便捷函数:建议文件放置位置"""
return path_handler.suggest_file_placement(filename)
return path_handler.suggest_file_placement(filename)

View File

@ -11,7 +11,7 @@ from panda3d.core import (
TransparencyAttrib, BoundingSphere, NodePath,
GraphicsEngine, WindowProperties, FrameBufferProperties,
GraphicsPipe, GraphicsOutput, Camera, DisplayRegion, OrthographicLens,
BoundingBox, BitMask32
BoundingBox, BitMask32, Material, MaterialAttrib, ColorAttrib, TextureAttrib, PNMImage
)
# p3dimgui.backend first tries `from shaders import *`, which can be shadowed by
@ -213,11 +213,87 @@ class SSBOEditor:
io.fonts.clear()
io.fonts.add_font_default()
def _fixBlackMaterials(self, model):
try:
from panda3d.core import MaterialAttrib, Material, GeomNode
for geom_path in model.findAllMatches('**/+GeomNode'):
geom_node = geom_path.node()
node_state = geom_path.getState()
if node_state.hasAttrib(MaterialAttrib.getClassType()):
mat_attrib = node_state.getAttrib(MaterialAttrib.getClassType())
mat = mat_attrib.getMaterial()
if mat:
is_black = False
if mat.hasBaseColor():
c = mat.getBaseColor()
if c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05:
is_black = True
elif mat.hasDiffuse():
c = mat.getDiffuse()
if c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05:
is_black = True
if is_black or not (mat.hasBaseColor() or mat.hasDiffuse()):
new_mat = Material(mat)
new_mat.setBaseColor((0.8, 0.8, 0.8, 1.0))
new_mat.setDiffuse((0.8, 0.8, 0.8, 1.0))
geom_path.setState(node_state.setAttrib(MaterialAttrib.make(new_mat)))
for i in range(geom_node.getNumGeoms()):
geom_state = geom_node.getGeomState(i)
if geom_state.hasAttrib(MaterialAttrib.getClassType()):
mat_attrib = geom_state.getAttrib(MaterialAttrib.getClassType())
mat = mat_attrib.getMaterial()
if mat:
is_black = False
if mat.hasBaseColor():
c = mat.getBaseColor()
if c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05:
is_black = True
elif mat.hasDiffuse():
c = mat.getDiffuse()
if c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05:
is_black = True
if is_black or not (mat.hasBaseColor() or mat.hasDiffuse()):
new_mat = Material(mat)
new_mat.setBaseColor((0.8, 0.8, 0.8, 1.0))
new_mat.setDiffuse((0.8, 0.8, 0.8, 1.0))
geom_node.setGeomState(i, geom_state.setAttrib(MaterialAttrib.make(new_mat)))
else:
new_mat = Material()
new_mat.setBaseColor((0.8, 0.8, 0.8, 1.0))
new_mat.setDiffuse((0.8, 0.8, 0.8, 1.0))
new_mat.setSpecular((0.2, 0.2, 0.2, 1.0))
new_mat.setRoughness(0.8)
geom_node.setGeomState(i, geom_state.addAttrib(MaterialAttrib.make(new_mat)))
model.clearColor()
except Exception as e:
print(f'修复黑色模型材质时出错: {e}')
def load_model(self, model_path):
"""Load and process a model using hybrid static/dynamic chunks."""
print(f"[SSBOEditor] Loading model: {model_path}")
fn = Filename.fromOsSpecific(model_path)
source_model = self.base.loader.loadModel(fn)
source_model = None
last_error = None
for fn in self._build_filename_candidates(model_path):
try:
source_model = self.base.loader.loadModel(fn)
if source_model and not source_model.is_empty():
break
except Exception as e:
last_error = e
source_model = None
if not source_model or source_model.is_empty():
if last_error:
raise RuntimeError(f"Failed to load model '{model_path}': {last_error}")
raise RuntimeError(f"Failed to load model '{model_path}'")
self._fixBlackMaterials(source_model)
self._repair_missing_textures(source_model, model_path)
model_name = os.path.basename(model_path)
if model_name:
source_model.set_name(model_name)
@ -241,6 +317,545 @@ class SSBOEditor:
print(f"[SSBOEditor] Model loaded. Total objects: {count}")
def _build_filename_candidates(self, path_text):
"""Build Filename candidates with wide-char first for Windows CJK paths."""
candidates = []
seen = set()
for ctor_name in ("fromOsSpecificW", "from_os_specific_w", "fromOsSpecific", "from_os_specific"):
ctor = getattr(Filename, ctor_name, None)
if not ctor:
continue
try:
fn = ctor(path_text)
key = fn.get_fullpath()
if key in seen:
continue
seen.add(key)
candidates.append(fn)
except Exception:
continue
if not candidates:
try:
candidates.append(Filename(path_text))
except Exception:
pass
return candidates
def _load_texture_from_path(self, texture_path):
"""Load texture with robust path constructors."""
for fn in self._build_filename_candidates(texture_path):
try:
tex = self.base.loader.loadTexture(fn)
if tex:
return tex
except Exception:
continue
return None
def _build_texture_search_dirs(self, model_path):
"""Build candidate directories for missing texture recovery."""
dirs = []
model_dir = os.path.dirname(os.path.abspath(model_path))
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def add_dir(path):
if not path:
return
path = os.path.normpath(path)
if path in dirs:
return
if os.path.isdir(path):
dirs.append(path)
add_dir(model_dir)
try:
if os.path.isdir(model_dir):
for item in os.listdir(model_dir):
if item.lower().endswith('.fbm'):
fbm_dir = os.path.join(model_dir, item)
if os.path.isdir(fbm_dir):
add_dir(fbm_dir)
for sub in ("textures", "texture", "tex", "assets", "materials"):
add_dir(os.path.join(fbm_dir, sub))
except Exception:
pass
for sub in ("textures", "texture", "tex", "assets", "materials"):
add_dir(os.path.join(model_dir, sub))
parent = model_dir
for _ in range(2):
parent = os.path.dirname(parent)
if not parent:
break
for sub in ("textures", "texture", "tex", "assets", "materials"):
add_dir(os.path.join(parent, sub))
add_dir(os.path.join(project_root, "Resources"))
add_dir(os.path.join(project_root, "Resources", "textures"))
add_dir(os.path.join(project_root, "Resources", "materials"))
add_dir(os.path.join(project_root, "Resources", "models"))
return dirs
def _index_texture_files(self, dirs, limit=30000):
"""Index texture files by basename for fast lookup."""
texture_exts = {".png", ".jpg", ".jpeg", ".tga", ".bmp", ".dds", ".ktx", ".ktx2", ".webp"}
index = {}
scanned = 0
for root_dir in dirs:
try:
for root, _, files in os.walk(root_dir):
for filename in files:
ext = os.path.splitext(filename)[1].lower()
if ext not in texture_exts:
continue
key = filename.lower()
if key not in index:
index[key] = os.path.join(root, filename)
scanned += 1
if scanned >= limit:
return index
except Exception:
continue
return index
def _repair_missing_textures(self, model_np, model_path):
"""
Repair broken texture paths by basename search; if unresolved, clear that
missing texture binding to avoid black PBR sampling.
"""
if not model_np or model_np.is_empty():
return
search_dirs = self._build_texture_search_dirs(model_path)
texture_index = self._index_texture_files(search_dirs)
fixed = 0
cleared = 0
white_tex = self._get_white_fallback_texture()
nodes = [model_np] + list(model_np.find_all_matches("**"))
for node in nodes:
if not node or node.is_empty():
continue
try:
stages = node.find_all_texture_stages()
except Exception:
try:
stages = node.findAllTextureStages()
except Exception:
continue
try:
stage_count = stages.get_num_texture_stages()
stage_at = stages.get_texture_stage
except Exception:
try:
stage_count = stages.getNumTextureStages()
stage_at = stages.getTextureStage
except Exception:
continue
for i in range(stage_count):
stage = stage_at(i)
if not stage:
continue
try:
tex = node.get_texture(stage)
except Exception:
tex = node.getTexture(stage)
if not tex:
continue
if self._texture_is_valid(tex):
continue
basename = self._extract_texture_basename(tex)
if not basename:
continue
replacement = texture_index.get(basename.lower())
if replacement:
try:
new_tex = self._load_texture_from_path(replacement)
if new_tex:
try:
node.set_texture(stage, new_tex, 1)
except Exception:
node.setTexture(stage, new_tex, 1)
fixed += 1
continue
except Exception:
pass
# Missing texture with no replacement: CLEAR the texture binding.
# Do NOT use a white fallback texture, because if this is a Metallic or Roughness
# map, pure white will force Metallic=1.0 and Roughness=1.0, which turns models black!
try:
try:
node.clear_texture(stage)
except Exception:
node.clearTexture(stage)
cleared += 1
except Exception:
pass
# GeomState-level texture bindings (common in imported FBX/GLTF):
# inspect and apply the same fallback on this GeomNode path.
try:
gnode = node.node()
get_num_geoms = getattr(gnode, "get_num_geoms", None) or getattr(gnode, "getNumGeoms", None)
get_geom_state = getattr(gnode, "get_geom_state", None) or getattr(gnode, "getGeomState", None)
geom_count = int(get_num_geoms()) if callable(get_num_geoms) else 0
except Exception:
geom_count = 0
for gi in range(geom_count):
try:
state = get_geom_state(gi)
except Exception:
continue
if state is None:
continue
tattr = None
try:
if state.has_attrib(TextureAttrib.get_class_type()):
tattr = state.get_attrib(TextureAttrib.get_class_type())
except Exception:
try:
if state.hasAttrib(TextureAttrib.getClassType()):
tattr = state.getAttrib(TextureAttrib.getClassType())
except Exception:
tattr = None
if not tattr:
continue
try:
num_on = tattr.get_num_on_stages()
get_stage = tattr.get_on_stage
get_tex = tattr.get_on_texture
except Exception:
try:
num_on = tattr.getNumOnStages()
get_stage = tattr.getOnStage
get_tex = tattr.getOnTexture
except Exception:
continue
for si in range(int(num_on)):
try:
stage = get_stage(si)
tex = get_tex(stage)
except Exception:
continue
if not stage or not tex:
continue
if self._texture_is_valid(tex):
continue
basename = self._extract_texture_basename(tex)
replacement = texture_index.get(basename.lower()) if basename else None
if replacement:
try:
new_tex = self._load_texture_from_path(replacement)
if new_tex:
try:
node.set_texture(stage, new_tex, 100000)
except Exception:
node.setTexture(stage, new_tex, 100000)
fixed += 1
continue
except Exception:
pass
try:
try:
node.clear_texture(stage)
except Exception:
node.clearTexture(stage)
cleared += 1
except Exception:
pass
if fixed or cleared:
print(f"[SSBOEditor] Texture repair: fixed={fixed}, cleared_missing={cleared}")
self._apply_nonblack_material_fallback(model_np)
def _texture_is_valid(self, tex):
if not tex:
return False
tex_path = self._extract_texture_os_path(tex)
if tex_path and os.path.exists(tex_path):
return True
if tex_path:
# Some valid textures use Panda VFS virtual paths; keep them RAM-checkable.
path_norm = tex_path.replace("\\", "/")
if path_norm.startswith("/$$") or path_norm.startswith("$$"):
pass
else:
# File-backed texture with missing source path should be considered invalid,
# even when Panda keeps a tiny fallback image in RAM.
return False
try:
if tex.has_ram_image():
return tex.get_ram_image_size() > 0
except Exception:
try:
if tex.hasRamImage():
return tex.getRamImageSize() > 0
except Exception:
pass
return False
def _extract_texture_os_path(self, tex):
tex_path = ""
try:
if tex.has_fullpath():
fullpath = tex.get_fullpath()
try:
tex_path = fullpath.to_os_specific()
except Exception:
try:
tex_path = fullpath.toOsSpecific()
except Exception:
tex_path = str(fullpath)
except Exception:
try:
if tex.hasFullpath():
fullpath = tex.getFullpath()
try:
tex_path = fullpath.toOsSpecific()
except Exception:
tex_path = str(fullpath)
except Exception:
tex_path = ""
tex_path = str(tex_path or "").strip()
if not tex_path:
return ""
tex_path = os.path.normpath(tex_path)
if os.path.exists(tex_path):
return tex_path
# Convert Panda internal drive path (/d/foo/bar) to Windows path if needed.
if len(tex_path) >= 3 and tex_path[0] in ("/", "\\") and tex_path[1].isalpha() and tex_path[2] in ("/", "\\"):
drive_path = f"{tex_path[1]}:{tex_path[2:]}"
drive_path = os.path.normpath(drive_path)
if os.path.exists(drive_path):
return drive_path
return tex_path
def _extract_texture_basename(self, tex):
tex_path = self._extract_texture_os_path(tex)
if tex_path:
return os.path.basename(tex_path.replace("\\", "/"))
try:
name = tex.get_name()
except Exception:
try:
name = tex.getName()
except Exception:
name = ""
return os.path.basename(str(name).replace("\\", "/"))
def _get_white_fallback_texture(self):
tex = getattr(self, "_ssbo_white_fallback_tex", None)
if tex:
return tex
try:
img = PNMImage(2, 2, 4)
img.fill(1.0, 1.0, 1.0)
img.alpha_fill(1.0)
tex = Texture("ssbo_white_fallback")
tex.load(img)
tex.set_minfilter(Texture.FT_nearest)
tex.set_magfilter(Texture.FT_nearest)
self._ssbo_white_fallback_tex = tex
return tex
except Exception:
return None
def _node_has_valid_texture(self, node):
if not node or node.is_empty():
return False
try:
stages = node.find_all_texture_stages()
stage_count = stages.get_num_texture_stages()
stage_at = stages.get_texture_stage
except Exception:
try:
stages = node.findAllTextureStages()
stage_count = stages.getNumTextureStages()
stage_at = stages.getTextureStage
except Exception:
return False
for i in range(stage_count):
stage = stage_at(i)
if not stage:
continue
try:
tex = node.get_texture(stage)
except Exception:
tex = node.getTexture(stage)
if self._texture_is_valid(tex):
return True
# GeomState TextureAttrib bindings can hold the effective textures.
try:
gnode = node.node()
get_num_geoms = getattr(gnode, "get_num_geoms", None) or getattr(gnode, "getNumGeoms", None)
get_geom_state = getattr(gnode, "get_geom_state", None) or getattr(gnode, "getGeomState", None)
geom_count = int(get_num_geoms()) if callable(get_num_geoms) else 0
except Exception:
geom_count = 0
for gi in range(geom_count):
try:
state = get_geom_state(gi)
except Exception:
continue
if state is None:
continue
tattr = None
try:
if state.has_attrib(TextureAttrib.get_class_type()):
tattr = state.get_attrib(TextureAttrib.get_class_type())
except Exception:
try:
if state.hasAttrib(TextureAttrib.getClassType()):
tattr = state.getAttrib(TextureAttrib.getClassType())
except Exception:
tattr = None
if not tattr:
continue
try:
num_on = tattr.get_num_on_stages()
get_stage = tattr.get_on_stage
get_tex = tattr.get_on_texture
except Exception:
try:
num_on = tattr.getNumOnStages()
get_stage = tattr.getOnStage
get_tex = tattr.getOnTexture
except Exception:
continue
for si in range(int(num_on)):
try:
stage = get_stage(si)
tex = get_tex(stage)
except Exception:
continue
if self._texture_is_valid(tex):
return True
return False
def _is_node_material_dark(self, node):
"""Heuristic: detect near-black material/color state."""
if not node or node.is_empty():
return False
try:
state = node.get_state()
except Exception:
try:
state = node.getState()
except Exception:
return True
def _vec_dark(v):
try:
return max(float(v[0]), float(v[1]), float(v[2])) < 0.08
except Exception:
try:
return max(float(v.x), float(v.y), float(v.z)) < 0.08
except Exception:
return False
# Material color
mat = None
try:
if state.has_attrib(MaterialAttrib.get_class_type()):
mat_attr = state.get_attrib(MaterialAttrib.get_class_type())
mat = mat_attr.get_material()
except Exception:
try:
if state.hasAttrib(MaterialAttrib.getClassType()):
mat_attr = state.getAttrib(MaterialAttrib.getClassType())
mat = mat_attr.getMaterial()
except Exception:
mat = None
if mat:
for has_name, get_name in (
("has_base_color", "get_base_color"),
("has_diffuse", "get_diffuse"),
("hasBaseColor", "getBaseColor"),
("hasDiffuse", "getDiffuse"),
):
has_fn = getattr(mat, has_name, None)
get_fn = getattr(mat, get_name, None)
if callable(has_fn) and callable(get_fn):
try:
if has_fn():
return _vec_dark(get_fn())
except Exception:
continue
return False
# ColorAttrib fallback
try:
if state.has_attrib(ColorAttrib.get_class_type()):
color_attr = state.get_attrib(ColorAttrib.get_class_type())
if not color_attr.is_off():
return _vec_dark(color_attr.get_color())
except Exception:
try:
if state.hasAttrib(ColorAttrib.getClassType()):
color_attr = state.getAttrib(ColorAttrib.getClassType())
if not color_attr.isOff():
return _vec_dark(color_attr.getColor())
except Exception:
pass
return True
def _apply_nonblack_material_fallback(self, model_np):
"""If a geom has no texture and is effectively black, apply a neutral material."""
if not model_np or model_np.is_empty():
return
neutral = Material()
neutral.set_base_color((0.75, 0.75, 0.75, 1.0)) if hasattr(neutral, "set_base_color") else neutral.setBaseColor((0.75, 0.75, 0.75, 1.0))
neutral.set_diffuse((0.75, 0.75, 0.75, 1.0)) if hasattr(neutral, "set_diffuse") else neutral.setDiffuse((0.75, 0.75, 0.75, 1.0))
neutral.set_ambient((0.22, 0.22, 0.22, 1.0)) if hasattr(neutral, "set_ambient") else neutral.setAmbient((0.22, 0.22, 0.22, 1.0))
neutral.set_specular((0.1, 0.1, 0.1, 1.0)) if hasattr(neutral, "set_specular") else neutral.setSpecular((0.1, 0.1, 0.1, 1.0))
neutral.set_shininess(8.0) if hasattr(neutral, "set_shininess") else neutral.setShininess(8.0)
patched = 0
for geom_np in model_np.find_all_matches("**/+GeomNode"):
if self._node_has_valid_texture(geom_np):
continue
if not self._is_node_material_dark(geom_np):
continue
try:
try:
geom_np.set_material(neutral, 1)
except Exception:
geom_np.setMaterial(neutral, 1)
try:
geom_np.set_color_scale(1.0, 1.0, 1.0, 1.0)
except Exception:
geom_np.setColorScale(1.0, 1.0, 1.0, 1.0)
patched += 1
except Exception:
continue
if patched:
print(f"[SSBOEditor] Applied non-black fallback material to {patched} geom nodes.")
# No custom effect needed — RP default rendering for maximum FPS
def _inject_ssbo_into_shadow_state(self, effect_path):

View File

@ -13,7 +13,6 @@ from direct.actor.Actor import Actor
from direct.showbase.ShowBaseGlobal import globalClock
from panda3d.core import TextNode, CardMaker, TextureStage, NodePath, Texture, TransparencyAttrib, CollisionTraverser, \
Point3
from core.InfoPanelManager import InfoPanelManager
# 获取渲染管线路径
# 在文件开头添加sys导入如果还没有的话
import sys
@ -97,8 +96,6 @@ class MainApp(ShowBase):
# 加载所有脚本e
self.script_manager.load_all_scripts_from_directory()
self.info_panel_manager = InfoPanelManager(self)
try:
# 再导入controller模块
from rpcore.util.movement_controller import MovementController
@ -538,8 +535,6 @@ class MainApp(ShowBase):
video_path=video_path,
size=absolute_scale
)
elif gui_type == "info_panel":
new_element = self.info_panel_manager.onCreateSampleInfoPanel()
if "scripts" in gui_info and new_element:
self.processScripts(new_element,gui_info["scripts"])

View File

@ -707,7 +707,18 @@ class AnimationTools:
resolved_source = source
if isinstance(source, (str, os.PathLike)):
src_text = os.fspath(source)
resolved_source = Filename.from_os_specific(src_text).get_fullpath()
resolved_source = src_text
for ctor_name in ("fromOsSpecificW", "from_os_specific_w", "fromOsSpecific", "from_os_specific"):
ctor = getattr(Filename, ctor_name, None)
if not ctor:
continue
try:
candidate = ctor(src_text).get_fullpath()
if candidate:
resolved_source = candidate
break
except Exception:
continue
actor = Actor(resolved_source)
# 无论是否已检测到动画名,都显式绑定一次,避免“有名字但无可播放控制”
try:

View File

@ -930,6 +930,8 @@ class AppActions:
# 检查场景文件
scene_file = os.path.join(project_path, "scenes", "scene.bam")
if os.path.exists(scene_file):
if getattr(self, "use_ssbo_mouse_picking", False) and callable(getattr(self, "_import_model_for_runtime", None)):
self.use_ssbo_scene_import = True
# 加载场景
try:
if self.scene_manager.loadScene(scene_file):
@ -1074,9 +1076,15 @@ class AppActions:
model_np = getattr(self.ssbo_editor, 'model', None)
# Keep legacy ray-pick fallback usable by adding a collision body.
if model_np:
try:
from scene import util as scene_util
normalized_model_path = scene_util.normalize_model_path(file_path)
except Exception:
normalized_model_path = file_path
# Apply vital tags manually since SSBO overrides SceneManager loader
model_np.setTag("model_path", file_path)
model_np.setTag("model_path", normalized_model_path)
model_np.setTag("original_path", file_path)
model_np.setTag("saved_model_path", normalized_model_path)
model_np.setTag("is_model_root", "1")
model_np.setTag("is_scene_element", "1")
model_np.setTag("file", os.path.basename(file_path))
@ -1135,28 +1143,30 @@ class AppActions:
self.add_error_message("模型导入失败")
return None
if hasattr(self.scene_manager, 'processMaterials'):
self.scene_manager.processMaterials(model_node)
if show_info_message:
self.add_info_message("已应用默认材质")
try:
model_node.clearMaterial()
model_node.clearTexture()
# SSBO 模式下保留原始材质/纹理,避免模型发黑。
if not getattr(self, "use_ssbo_mouse_picking", False):
if hasattr(self.scene_manager, 'processMaterials'):
self.scene_manager.processMaterials(model_node)
if show_info_message:
self.add_info_message("已应用默认材质")
try:
color = model_node.getColor()
if color and len(color) >= 4 and color == (1, 1, 1, 1):
model_node.clearMaterial()
model_node.clearTexture()
if hasattr(self.scene_manager, 'processMaterials'):
self.scene_manager.processMaterials(model_node)
try:
color = model_node.getColor()
if color and len(color) >= 4 and color == (1, 1, 1, 1):
model_node.setColor(0.8, 0.8, 0.8, 1.0)
elif not color:
model_node.setColor(0.8, 0.8, 0.8, 1.0)
except Exception:
model_node.setColor(0.8, 0.8, 0.8, 1.0)
elif not color:
model_node.setColor(0.8, 0.8, 0.8, 1.0)
except Exception:
model_node.setColor(0.8, 0.8, 0.8, 1.0)
except Exception as e:
self.add_warning_message(f"材质处理警告: {e}")
except Exception as e:
self.add_warning_message(f"材质处理警告: {e}")
if set_origin:
model_node.setPos(0, 0, 0)

View File

@ -2229,7 +2229,10 @@ class EditorPanels:
# 删除对象
imgui.same_line()
if imgui.button("删除"):
if hasattr(self, 'selection') and self.selection:
if hasattr(self.app, "_on_delete") and callable(self.app._on_delete):
self.app._on_delete()
elif hasattr(self, 'selection') and self.selection and hasattr(self.selection, "deleteSelectedNode"):
# 兼容旧选择系统接口
self.selection.deleteSelectedNode()

View File

@ -339,51 +339,6 @@ class ObjectFactory:
except Exception as e:
print(f"✗ 创建平面失败: {e}")
return None
def create2DSamplePanel(self):
"""创建2D示例面板"""
try:
from core.InfoPanelManager import createSampleInfoPanel
result = createSampleInfoPanel(self.render)
return result
except Exception as e:
print(f"创建2D示例面板失败: {e}")
return None
def create3DSamplePanel(self):
"""创建3D实例面板"""
try:
if hasattr(self, 'info_panel_manager') and self.info_panel_manager:
# 创建3D信息面板
panel_id = f"3d_sample_{int(time.time())}"
result = self.info_panel_manager.create3DInfoPanel(
panel_id=panel_id,
position=(0, 0, 2),
size=(1.0, 0.6),
bg_color=(0.15, 0.25, 0.35, 0.95),
border_color=(0.3, 0.5, 0.7, 1.0),
title_color=(0.7, 0.9, 1.0, 1.0),
content_color=(0.95, 0.95, 0.95, 1.0)
)
# 添加示例内容
if result:
sample_data = {
"标题": "3D信息面板",
"状态": "运行中",
"创建时间": time.strftime("%Y-%m-%d %H:%M:%S"),
"位置": f"X:0, Y:0, Z:2"
}
self.info_panel_manager.updatePanelContent(panel_id, content=sample_data)
return result
return None
except Exception as e:
print(f"创建3D示例面板失败: {e}")
return None
def createWebPanel(self, url="https://www.example.com"):
"""创建Web面板"""

File diff suppressed because it is too large Load Diff