优化导入速度
This commit is contained in:
parent
f9f060b1ac
commit
78ffa8efba
@ -413,6 +413,44 @@ class EventHandler:
|
||||
or lowered.startswith("gizmo")
|
||||
)
|
||||
|
||||
def _resolve_model_root(node):
|
||||
current = node
|
||||
model_list = self.world.models if hasattr(self.world, "models") else []
|
||||
while _is_valid_node(current) and current != self.world.render:
|
||||
try:
|
||||
if current in model_list or current.hasTag("is_model_root"):
|
||||
return current
|
||||
current = current.getParent()
|
||||
except Exception:
|
||||
break
|
||||
return None
|
||||
|
||||
# In SSBO mode, animated/legacy models still rely on modelCollision_* for picking,
|
||||
# so we cannot ignore those hits unconditionally.
|
||||
# Only treat a hit on the *currently selected same model root* as blank-space clear.
|
||||
if getattr(self.world, "use_ssbo_mouse_picking", False):
|
||||
try:
|
||||
hit_name = hitNode.getName() or ""
|
||||
except Exception:
|
||||
hit_name = ""
|
||||
if hit_name.lower().startswith("modelcollision_"):
|
||||
current_selected = None
|
||||
try:
|
||||
current_selected = self.world.selection.getSelectedNode()
|
||||
except Exception:
|
||||
current_selected = getattr(getattr(self, "world", None), "selection", None)
|
||||
owner_root = _resolve_model_root(hitNode)
|
||||
try:
|
||||
same_root = bool(
|
||||
current_selected and owner_root and current_selected == owner_root
|
||||
)
|
||||
except Exception:
|
||||
same_root = False
|
||||
if same_root:
|
||||
print("SSBO 模式下命中当前选中模型的辅助碰撞壳,按空白区域清除选择")
|
||||
self.world.selection.updateSelection(None)
|
||||
return
|
||||
|
||||
def _is_selectable_scene_node(node):
|
||||
if not _is_valid_node(node) or node == self.world.render:
|
||||
return False
|
||||
|
||||
@ -78,7 +78,7 @@ Size=93,65
|
||||
Collapsed=0
|
||||
|
||||
[Window][新建项目]
|
||||
Pos=760,366
|
||||
Pos=760,354
|
||||
Size=400,300
|
||||
Collapsed=0
|
||||
|
||||
@ -206,7 +206,7 @@ Collapsed=0
|
||||
DockId=0x00000005,2
|
||||
|
||||
[Window][项目另存为]
|
||||
Pos=730,396
|
||||
Pos=730,384
|
||||
Size=460,240
|
||||
Collapsed=0
|
||||
|
||||
|
||||
@ -87,9 +87,6 @@ class AssetDatabase:
|
||||
}
|
||||
)
|
||||
|
||||
if asset_type == "model":
|
||||
self._build_model_import_cache(record)
|
||||
|
||||
assets[asset_guid] = record
|
||||
if previous_asset_path != relative_asset_path:
|
||||
changed = True
|
||||
@ -290,7 +287,13 @@ class AssetDatabase:
|
||||
"import_info": relative_project_path(self.layout.project_root, import_info_path),
|
||||
}
|
||||
|
||||
def register_asset(self, asset_path: str, preferred_subdir: str = "", copy_into_assets: bool = False) -> dict:
|
||||
def register_asset(
|
||||
self,
|
||||
asset_path: str,
|
||||
preferred_subdir: str = "",
|
||||
copy_into_assets: bool = False,
|
||||
build_import_cache: bool = False,
|
||||
) -> dict:
|
||||
asset_path = normalize_path(asset_path)
|
||||
if not os.path.exists(asset_path):
|
||||
return {}
|
||||
@ -323,7 +326,7 @@ class AssetDatabase:
|
||||
}
|
||||
)
|
||||
|
||||
if asset_type == "model":
|
||||
if asset_type == "model" and build_import_cache:
|
||||
self._build_model_import_cache(record)
|
||||
|
||||
meta_payload = {
|
||||
@ -341,8 +344,13 @@ class AssetDatabase:
|
||||
self.save()
|
||||
return dict(record)
|
||||
|
||||
def import_asset(self, source_path: str, preferred_subdir: str = "") -> dict:
|
||||
return self.register_asset(source_path, preferred_subdir=preferred_subdir, copy_into_assets=True)
|
||||
def import_asset(self, source_path: str, preferred_subdir: str = "", build_import_cache: bool = False) -> dict:
|
||||
return self.register_asset(
|
||||
source_path,
|
||||
preferred_subdir=preferred_subdir,
|
||||
copy_into_assets=True,
|
||||
build_import_cache=build_import_cache,
|
||||
)
|
||||
|
||||
def ensure_project_assets_registered(self):
|
||||
self._sync_assets_from_meta_scan()
|
||||
@ -351,4 +359,4 @@ class AssetDatabase:
|
||||
if file_name.endswith(".meta"):
|
||||
continue
|
||||
asset_path = os.path.join(root, file_name)
|
||||
self.register_asset(asset_path, copy_into_assets=False)
|
||||
self.register_asset(asset_path, copy_into_assets=False, build_import_cache=False)
|
||||
|
||||
@ -9,6 +9,9 @@ import struct
|
||||
import tempfile
|
||||
|
||||
|
||||
_GLTF_METADATA_CACHE = {}
|
||||
|
||||
|
||||
def is_gltf_path(file_path: str) -> bool:
|
||||
ext = os.path.splitext(str(file_path or ""))[1].lower()
|
||||
return ext in {".gltf", ".glb"}
|
||||
@ -88,20 +91,49 @@ def _load_gltf_json_payload(file_path: str):
|
||||
|
||||
|
||||
def probe_gltf_metadata(file_path: str) -> dict:
|
||||
payload = _load_gltf_json_payload(file_path)
|
||||
if not payload:
|
||||
file_path = _to_os_specific_path(file_path)
|
||||
if not file_path or not os.path.exists(file_path):
|
||||
return {
|
||||
"is_gltf": False,
|
||||
"has_animations": False,
|
||||
"animation_count": 0,
|
||||
}
|
||||
|
||||
try:
|
||||
stat_info = os.stat(file_path)
|
||||
cache_key = (
|
||||
os.path.abspath(file_path),
|
||||
int(stat_info.st_mtime_ns),
|
||||
int(stat_info.st_size),
|
||||
)
|
||||
cached = _GLTF_METADATA_CACHE.get(cache_key)
|
||||
if cached is not None:
|
||||
return dict(cached)
|
||||
except Exception:
|
||||
cache_key = None
|
||||
|
||||
payload = _load_gltf_json_payload(file_path)
|
||||
if not payload:
|
||||
result = {
|
||||
"is_gltf": False,
|
||||
"has_animations": False,
|
||||
"animation_count": 0,
|
||||
}
|
||||
if cache_key is not None:
|
||||
_GLTF_METADATA_CACHE.clear()
|
||||
_GLTF_METADATA_CACHE[cache_key] = dict(result)
|
||||
return result
|
||||
|
||||
animations = payload.get("animations") or []
|
||||
return {
|
||||
result = {
|
||||
"is_gltf": True,
|
||||
"has_animations": bool(animations),
|
||||
"animation_count": len(animations),
|
||||
}
|
||||
if cache_key is not None:
|
||||
_GLTF_METADATA_CACHE.clear()
|
||||
_GLTF_METADATA_CACHE[cache_key] = dict(result)
|
||||
return result
|
||||
|
||||
|
||||
def _resolve_cache_root(project_root: str = "") -> str:
|
||||
|
||||
@ -87,27 +87,30 @@ class SceneManagerModelMixin:
|
||||
gltf_meta = None
|
||||
|
||||
try:
|
||||
from scene.gltf_support import ensure_gltf_visual_bam, probe_gltf_metadata
|
||||
from scene.gltf_support import get_gltf_visual_bam_path, probe_gltf_metadata
|
||||
|
||||
gltf_meta = probe_gltf_metadata(filepath)
|
||||
if gltf_meta.get("is_gltf"):
|
||||
has_anim = gltf_meta.get("has_animations", False)
|
||||
# 智能加载策略:
|
||||
# 1. 如果模型有动画,则强制使用 panda3d-gltf 构建的可见性缓存(BAM),以保证动画支持。
|
||||
# 1. 如果模型有动画,仅在缓存已存在时使用缓存;首次导入直接加载原始 glTF,
|
||||
# 避免同步构建 BAM 导致首次导入被完整解析两次。
|
||||
# 2. 如果模型是纯静态场景,则跳过缓存,使用原生加载器(如 Assimp),这样在大场景下更流畅。
|
||||
if has_anim:
|
||||
project_manager = getattr(getattr(self, "world", None), "project_manager", None)
|
||||
project_root = getattr(project_manager, "current_project_path", "") if project_manager else ""
|
||||
|
||||
cached_visual_path = ensure_gltf_visual_bam(
|
||||
|
||||
cached_visual_path = get_gltf_visual_bam_path(
|
||||
filepath,
|
||||
project_root=project_root,
|
||||
skip_animations=False, # 有动画的模型不应跳过动画
|
||||
flatten_nodes=False,
|
||||
)
|
||||
if cached_visual_path and cached_visual_path != filepath:
|
||||
if cached_visual_path and cached_visual_path != filepath and os.path.exists(cached_visual_path):
|
||||
visual_load_path = cached_visual_path
|
||||
print(f"[GLTF智能加载] 检测到动画,使用 panda3d-gltf 缓存: {cached_visual_path}")
|
||||
else:
|
||||
print(f"[GLTF智能加载] 检测到动画,首次导入跳过同步BAM构建: {filepath}")
|
||||
else:
|
||||
print(f"[GLTF智能加载] 纯静态模型,跳过缓存以开启流畅模式: {filepath}")
|
||||
except Exception as e:
|
||||
@ -844,33 +847,29 @@ class SceneManagerModelMixin:
|
||||
cNode.setIntoCollideMask(current_mask | model_collision_mask)
|
||||
print(f"为 {model.getName()} 启用模型间碰撞检测")
|
||||
|
||||
# 获取模型的边界信息,使用与选择框相同的计算方法
|
||||
minPoint = Point3()
|
||||
maxPoint = Point3()
|
||||
|
||||
# 使用与选择框相同的calcTightBounds方法获取边界,但是在局部坐标系中进行计算
|
||||
# 这样计算出的包围盒直接贴合几何体,并且无论模型自身受到什么平移/缩放/旋转都不会发生两次形变!
|
||||
if model.calcTightBounds(minPoint, maxPoint, model):
|
||||
# 检查边界框的有效性
|
||||
if (abs(minPoint.x) < 1e10 and abs(minPoint.y) < 1e10 and abs(minPoint.z) < 1e10 and
|
||||
abs(maxPoint.x) < 1e10 and abs(maxPoint.y) < 1e10 and abs(maxPoint.z) < 1e10):
|
||||
|
||||
# 我们现在获取的是纯局部几何数据,因此不再需要手动乘以100或应用旋转来抵消FBX形变
|
||||
|
||||
# 创建与选择框完全一致的碰撞体
|
||||
cBox = CollisionBox(minPoint, maxPoint)
|
||||
cNode.addSolid(cBox)
|
||||
radius = max(maxPoint.x - minPoint.x, maxPoint.y - minPoint.y, maxPoint.z - minPoint.z) / 2
|
||||
else:
|
||||
# 使用默认球体
|
||||
radius = 1.0
|
||||
cSphere = CollisionSphere(Point3(0, 0, 0), radius)
|
||||
cNode.addSolid(cSphere)
|
||||
else:
|
||||
# 使用默认球体
|
||||
# 导入阶段避免使用 calcTightBounds 扫描全部几何体。
|
||||
# 这里优先使用 Panda 已有包围体快速生成一个可用于选择/碰撞的近似球体。
|
||||
radius = 1.0
|
||||
center = Point3(0, 0, 0)
|
||||
try:
|
||||
bounds = model.getBounds()
|
||||
if bounds and not bounds.isEmpty():
|
||||
try:
|
||||
center = bounds.getApproxCenter()
|
||||
except Exception:
|
||||
center = bounds.getCenter()
|
||||
try:
|
||||
radius = float(bounds.getRadius())
|
||||
except Exception:
|
||||
radius = 1.0
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not (radius > 0.0 and radius < 1e10):
|
||||
radius = 1.0
|
||||
cSphere = CollisionSphere(Point3(0, 0, 0), radius)
|
||||
cNode.addSolid(cSphere)
|
||||
|
||||
cSphere = CollisionSphere(center, radius)
|
||||
cNode.addSolid(cSphere)
|
||||
|
||||
# 将碰撞节点附加到模型上
|
||||
cNodePath = model.attachNewNode(cNode)
|
||||
|
||||
@ -22,16 +22,17 @@ class CrossPlatformPathHandler:
|
||||
|
||||
print(f"路径处理器初始化 - 系统: {self.system}")
|
||||
|
||||
def normalize_model_path(self, filepath):
|
||||
"""标准化模型文件路径"""
|
||||
try:
|
||||
def normalize_model_path(self, filepath):
|
||||
"""标准化模型文件路径"""
|
||||
try:
|
||||
#print(f"\n=== 路径标准化处理 ===")
|
||||
#print(f"原始路径: {filepath}")
|
||||
#print(f"当前系统: {self.system}")
|
||||
|
||||
# 步骤1: 检查原始路径是否存在
|
||||
if self._check_file_exists(filepath):
|
||||
return self._panda3d_normalize(filepath)
|
||||
# 步骤1: 检查原始路径是否存在
|
||||
if self._check_file_exists(filepath):
|
||||
existing_path = self._to_os_specific_existing_path(filepath) or filepath
|
||||
return self._panda3d_normalize(existing_path)
|
||||
|
||||
# 步骤2: 路径修复尝试
|
||||
fixed_path = self._attempt_path_fixes(filepath)
|
||||
@ -51,10 +52,54 @@ class CrossPlatformPathHandler:
|
||||
print(f"❌ 路径标准化失败: {e}")
|
||||
return filepath
|
||||
|
||||
def _check_file_exists(self, filepath):
|
||||
"""检查文件是否存在"""
|
||||
exists = os.path.exists(filepath)
|
||||
return exists
|
||||
def _check_file_exists(self, filepath):
|
||||
"""检查文件是否存在"""
|
||||
try:
|
||||
if filepath and os.path.exists(filepath):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
os_path = self._to_os_specific_existing_path(filepath)
|
||||
if os_path and os.path.exists(os_path):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
def _to_os_specific_existing_path(self, filepath):
|
||||
"""将 Panda 风格路径转换为当前系统下真实存在的路径。"""
|
||||
path_text = os.fspath(filepath or "")
|
||||
if not path_text:
|
||||
return ""
|
||||
if os.path.exists(path_text):
|
||||
return os.path.normpath(path_text)
|
||||
|
||||
try:
|
||||
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(path_text).to_os_specific()
|
||||
if candidate and os.path.exists(candidate):
|
||||
return os.path.normpath(candidate)
|
||||
except Exception:
|
||||
continue
|
||||
candidate = Filename(path_text).to_os_specific()
|
||||
if candidate and os.path.exists(candidate):
|
||||
return os.path.normpath(candidate)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if len(path_text) >= 3 and path_text[0] in ("/", "\\") and path_text[1].isalpha() and path_text[2] in ("/", "\\"):
|
||||
drive_path = f"{path_text[1].upper()}:{path_text[2:]}"
|
||||
drive_path = os.path.normpath(drive_path)
|
||||
if os.path.exists(drive_path):
|
||||
return drive_path
|
||||
|
||||
return ""
|
||||
|
||||
def _panda3d_normalize(self, filepath):
|
||||
"""使用Panda3D标准化路径"""
|
||||
|
||||
@ -2875,6 +2875,14 @@ class SSBOEditor:
|
||||
if self.pick_object(mpos.x, mpos.y):
|
||||
return
|
||||
|
||||
# In SSBO picking mode, a miss should clear the current SSBO selection.
|
||||
# Falling back to legacy collision picking here tends to immediately
|
||||
# re-hit broad helper/collision shells from the selected root model,
|
||||
# which makes "click blank space to deselect" fail for top-level nodes.
|
||||
if self.has_active_selection():
|
||||
self.clear_selection()
|
||||
return
|
||||
|
||||
try:
|
||||
win_width, win_height = self.base.win.getSize()
|
||||
window_x = (float(mpos.x) + 1.0) * 0.5 * float(win_width)
|
||||
|
||||
@ -1095,7 +1095,9 @@ class AnimationTools:
|
||||
except Exception as e:
|
||||
print(f"[Actor加载调试] 获取 tags 异常: {e}")
|
||||
|
||||
filepath = owner_model.getTag("model_path") if owner_model.hasTag("model_path") else ""
|
||||
filepath = owner_model.getTag("resolved_actor_path") if owner_model.hasTag("resolved_actor_path") else ""
|
||||
if not filepath and owner_model.hasTag("model_path"):
|
||||
filepath = owner_model.getTag("model_path")
|
||||
if not filepath and owner_model.hasTag("original_path"):
|
||||
filepath = owner_model.getTag("original_path")
|
||||
|
||||
@ -1138,12 +1140,16 @@ class AnimationTools:
|
||||
candidate_paths.append(win_path)
|
||||
candidate_paths.append(os.path.normpath(win_path))
|
||||
|
||||
# 尝试 Panda3D 路径标准化
|
||||
try:
|
||||
from scene import util
|
||||
candidate_paths.append(util.normalize_model_path(filepath))
|
||||
except Exception:
|
||||
pass
|
||||
# 仅在当前路径和基础变体都不可直接访问时,才走较重的路径修复/搜索。
|
||||
should_normalize_search = not any(os.path.exists(p) for p in candidate_paths if isinstance(p, str) and p)
|
||||
if should_normalize_search:
|
||||
try:
|
||||
from scene import util
|
||||
normalized_candidate = util.normalize_model_path(filepath)
|
||||
if normalized_candidate:
|
||||
candidate_paths.append(normalized_candidate)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 在 Resources/models 中按文件名兜底查找
|
||||
filename = os.path.basename(filepath)
|
||||
@ -1172,6 +1178,7 @@ class AnimationTools:
|
||||
actor = _try_create_actor_from_source(p, f"文件路径({p})")
|
||||
if actor:
|
||||
owner_model.setTag("model_path", p)
|
||||
owner_model.setTag("resolved_actor_path", p)
|
||||
owner_model.setTag("has_animations", "true")
|
||||
self._actor_cache[owner_model] = actor
|
||||
return actor
|
||||
@ -1180,6 +1187,7 @@ class AnimationTools:
|
||||
actor = _try_create_actor_via_gltf_path(p)
|
||||
if actor:
|
||||
owner_model.setTag("model_path", p)
|
||||
owner_model.setTag("resolved_actor_path", p)
|
||||
owner_model.setTag("has_animations", "true")
|
||||
self._actor_cache[owner_model] = actor
|
||||
return actor
|
||||
@ -1196,6 +1204,7 @@ class AnimationTools:
|
||||
loaded_model.reparentTo(self.render)
|
||||
loaded_model.hide()
|
||||
owner_model.setTag("model_path", p)
|
||||
owner_model.setTag("resolved_actor_path", p)
|
||||
owner_model.setTag("has_animations", "true")
|
||||
self._actor_cache[owner_model] = proxy
|
||||
return proxy
|
||||
|
||||
@ -46,12 +46,32 @@ class AppActions:
|
||||
try:
|
||||
if not file_path or not os.path.exists(file_path):
|
||||
return False
|
||||
try:
|
||||
stat_info = os.stat(file_path)
|
||||
cache_key = (
|
||||
os.path.abspath(file_path),
|
||||
int(stat_info.st_mtime_ns),
|
||||
int(stat_info.st_size),
|
||||
)
|
||||
animation_cache = getattr(self, "_model_animation_probe_cache", None)
|
||||
if animation_cache is None:
|
||||
animation_cache = {}
|
||||
self._model_animation_probe_cache = animation_cache
|
||||
cached = animation_cache.get(cache_key)
|
||||
if cached is not None:
|
||||
return bool(cached)
|
||||
except Exception:
|
||||
cache_key = None
|
||||
try:
|
||||
from scene.gltf_support import probe_gltf_metadata
|
||||
|
||||
gltf_meta = probe_gltf_metadata(file_path)
|
||||
if gltf_meta.get("is_gltf"):
|
||||
return bool(gltf_meta.get("has_animations"))
|
||||
result = bool(gltf_meta.get("has_animations"))
|
||||
if cache_key is not None:
|
||||
animation_cache.clear()
|
||||
animation_cache[cache_key] = result
|
||||
return result
|
||||
except Exception:
|
||||
pass
|
||||
loader = getattr(self, "loader", None)
|
||||
@ -76,7 +96,11 @@ class AppActions:
|
||||
model.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
return bool(has_animation)
|
||||
result = bool(has_animation)
|
||||
if cache_key is not None:
|
||||
animation_cache.clear()
|
||||
animation_cache[cache_key] = result
|
||||
return result
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
@ -1,8 +1,69 @@
|
||||
from imgui_bundle import imgui, imgui_ctx
|
||||
import re
|
||||
|
||||
class EditorPanelsRightMaterialMixin:
|
||||
"""Auto-split mixin from editor_panels_right.py."""
|
||||
|
||||
def _get_material_display_name(self, material, index):
|
||||
name = ""
|
||||
try:
|
||||
if hasattr(material, "get_name"):
|
||||
name = material.get_name()
|
||||
if name:
|
||||
name = str(name)
|
||||
except Exception:
|
||||
pass
|
||||
if not name:
|
||||
try:
|
||||
if hasattr(material, "getName"):
|
||||
name = material.getName()
|
||||
if name:
|
||||
name = str(name)
|
||||
except Exception:
|
||||
pass
|
||||
if not name:
|
||||
return f"材质{index + 1}"
|
||||
clean_name = re.sub(r"__editable__.*$", "", name).strip()
|
||||
return clean_name or f"材质{index + 1}"
|
||||
|
||||
def _get_material_stable_key(self, material, index):
|
||||
identity_fn = getattr(self.app, "_get_material_identity_key", None)
|
||||
if callable(identity_fn):
|
||||
try:
|
||||
return str(identity_fn(material))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
return str(getattr(material, "this", None) or id(material))
|
||||
except Exception:
|
||||
return f"material_{index}"
|
||||
|
||||
def _get_materials_for_panel_display(self, node):
|
||||
"""Read-only material lookup for panel rendering; avoid mutating scene state every frame."""
|
||||
self._sync_material_panel_target(node)
|
||||
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 sorted(
|
||||
materials,
|
||||
key=lambda material: (
|
||||
self._get_material_display_name(material, 0).lower(),
|
||||
self._get_material_stable_key(material, 0),
|
||||
),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
materials = self.app._get_node_materials(node)
|
||||
return sorted(
|
||||
materials,
|
||||
key=lambda material: (
|
||||
self._get_material_display_name(material, 0).lower(),
|
||||
self._get_material_stable_key(material, 0),
|
||||
),
|
||||
)
|
||||
|
||||
def _ensure_material_edit_sessions(self):
|
||||
if not hasattr(self, "_material_edit_sessions"):
|
||||
self._material_edit_sessions = {}
|
||||
@ -112,7 +173,7 @@ class EditorPanelsRightMaterialMixin:
|
||||
|
||||
def _draw_appearance_properties(self, node):
|
||||
"""绘制材质属性(Unity风格主材质入口)。"""
|
||||
materials = self._ensure_node_materials_are_editable(node)
|
||||
materials = self._get_materials_for_panel_display(node)
|
||||
if not materials:
|
||||
fallback_material = self.app._ensure_material_for_node(node)
|
||||
materials = [fallback_material] if fallback_material else []
|
||||
@ -222,16 +283,17 @@ class EditorPanelsRightMaterialMixin:
|
||||
|
||||
def _draw_material_properties(self, node):
|
||||
"""绘制材质属性"""
|
||||
materials = self._ensure_node_materials_are_editable(node)
|
||||
materials = self._get_materials_for_panel_display(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}"
|
||||
material_name = self._get_material_display_name(material, i)
|
||||
material_key = self._get_material_stable_key(material, i)
|
||||
|
||||
if imgui.collapsing_header(f"材质: {material_name}"):
|
||||
if imgui.collapsing_header(f"材质: {material_name}##{material_key}"):
|
||||
# PBR属性
|
||||
imgui.text("PBR")
|
||||
if hasattr(material, 'roughness') and material.roughness is not None:
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from imgui_bundle import imgui, imgui_ctx
|
||||
@ -1358,6 +1359,7 @@ class PropertyHelpers:
|
||||
except Exception:
|
||||
source_name = ""
|
||||
|
||||
source_name = re.sub(r"__editable__.*$", "", str(source_name or "")).strip()
|
||||
node_name = self._get_node_name(node, "node") if hasattr(self, "_get_node_name") else "node"
|
||||
clone_name = f"{source_name or 'material'}__editable__{node_name}"
|
||||
try:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user