保存读取优化
This commit is contained in:
parent
eb18e84bc9
commit
3168996afb
@ -210,7 +210,7 @@ Size=460,240
|
||||
Collapsed=0
|
||||
|
||||
[Window][打包项目]
|
||||
Pos=754,392
|
||||
Pos=681,290
|
||||
Size=540,320
|
||||
Collapsed=0
|
||||
|
||||
|
||||
@ -173,6 +173,39 @@ class AssetDatabase:
|
||||
with open(meta_path, "w", encoding="utf-8") as f:
|
||||
json.dump(payload, f, ensure_ascii=False, indent=4)
|
||||
|
||||
def _read_json_file(self, json_path: str) -> dict:
|
||||
if not os.path.exists(json_path):
|
||||
return {}
|
||||
try:
|
||||
with open(json_path, "r", encoding="utf-8") as f:
|
||||
payload = json.load(f)
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def _build_model_import_cache_record(self, asset_record: dict, cache_dir: str, model_bam_path: str, hierarchy_path: str, materials_path: str, import_info_path: str) -> dict:
|
||||
return {
|
||||
"root": relative_project_path(self.layout.project_root, cache_dir),
|
||||
"model_bam": relative_project_path(self.layout.project_root, model_bam_path),
|
||||
"hierarchy": relative_project_path(self.layout.project_root, hierarchy_path),
|
||||
"materials": relative_project_path(self.layout.project_root, materials_path),
|
||||
"import_info": relative_project_path(self.layout.project_root, import_info_path),
|
||||
}
|
||||
|
||||
def _can_reuse_model_import_cache(self, asset_record: dict, *, model_bam_path: str, hierarchy_path: str, materials_path: str, import_info_path: str) -> bool:
|
||||
if not all(os.path.exists(path) for path in (model_bam_path, hierarchy_path, materials_path, import_info_path)):
|
||||
return False
|
||||
|
||||
import_info = self._read_json_file(import_info_path)
|
||||
if not import_info:
|
||||
return False
|
||||
if str(import_info.get("asset_guid", "") or "").strip() != str(asset_record.get("guid", "") or "").strip():
|
||||
return False
|
||||
if str(import_info.get("source_hash", "") or "").strip() != str(asset_record.get("source_hash", "") or "").strip():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _copy_into_assets(self, source_path: str, preferred_subdir: str = "") -> str:
|
||||
source_path = normalize_path(source_path)
|
||||
asset_type = detect_asset_type(source_path)
|
||||
@ -207,6 +240,24 @@ class AssetDatabase:
|
||||
hierarchy_path = os.path.join(cache_dir, "hierarchy.json")
|
||||
materials_path = os.path.join(cache_dir, "materials.json")
|
||||
import_info_path = os.path.join(cache_dir, "import_info.json")
|
||||
imported_cache = self._build_model_import_cache_record(
|
||||
asset_record,
|
||||
cache_dir,
|
||||
model_bam_path,
|
||||
hierarchy_path,
|
||||
materials_path,
|
||||
import_info_path,
|
||||
)
|
||||
|
||||
if self._can_reuse_model_import_cache(
|
||||
asset_record,
|
||||
model_bam_path=model_bam_path,
|
||||
hierarchy_path=hierarchy_path,
|
||||
materials_path=materials_path,
|
||||
import_info_path=import_info_path,
|
||||
):
|
||||
asset_record["imported_cache"] = imported_cache
|
||||
return
|
||||
|
||||
try:
|
||||
model_np = self.world.loader.loadModel(Filename.fromOsSpecific(asset_path))
|
||||
@ -282,13 +333,7 @@ class AssetDatabase:
|
||||
indent=4,
|
||||
)
|
||||
|
||||
asset_record["imported_cache"] = {
|
||||
"root": relative_project_path(self.layout.project_root, cache_dir),
|
||||
"model_bam": relative_project_path(self.layout.project_root, model_bam_path),
|
||||
"hierarchy": relative_project_path(self.layout.project_root, hierarchy_path),
|
||||
"materials": relative_project_path(self.layout.project_root, materials_path),
|
||||
"import_info": relative_project_path(self.layout.project_root, import_info_path),
|
||||
}
|
||||
asset_record["imported_cache"] = imported_cache
|
||||
|
||||
def register_asset(self, asset_path: str, preferred_subdir: str = "", copy_into_assets: bool = False) -> dict:
|
||||
asset_path = normalize_path(asset_path)
|
||||
|
||||
@ -1450,6 +1450,7 @@ class ProjectManager:
|
||||
return False
|
||||
|
||||
refresh_runtime_fn = getattr(ssbo_editor, "refresh_runtime_from_source", None)
|
||||
normalize_source_dirty_fn = getattr(ssbo_editor, "normalize_source_transform_dirty_tags", None)
|
||||
|
||||
top_level_asset_nodes, node_lookup = self._iter_top_level_scene_asset_nodes(scene_description)
|
||||
if not top_level_asset_nodes:
|
||||
@ -1540,6 +1541,11 @@ class ProjectManager:
|
||||
node_lookup,
|
||||
apply_material_state=False,
|
||||
)
|
||||
if callable(normalize_source_dirty_fn):
|
||||
try:
|
||||
normalize_source_dirty_fn(target_root)
|
||||
except Exception:
|
||||
pass
|
||||
# Only rebuild between imports, or once after the whole batch below.
|
||||
# Otherwise reopening a project pays one extra full SSBO rebuild per model.
|
||||
if has_more_assets and callable(refresh_runtime_fn):
|
||||
|
||||
@ -651,6 +651,7 @@ class ObjectController:
|
||||
static_np = chunk["dynamic_np"].copy_to(self.model)
|
||||
static_np.set_name(f"chunk_{chunk_id:04d}_static")
|
||||
static_np.unstash()
|
||||
self._clear_string_tags_in_subtree(static_np)
|
||||
# Editor idle performance depends on the static chunk being actually batched.
|
||||
# The merged material/state preservation changes above keep per-geom render
|
||||
# state intact, so we can restore the pre-merge flattening behavior here.
|
||||
@ -665,6 +666,58 @@ class ObjectController:
|
||||
else:
|
||||
static_np.unstash()
|
||||
|
||||
def _clear_string_tags_in_subtree(self, root_np):
|
||||
"""Static chunk copies do not need scene/editor tags; remove them before flatten."""
|
||||
if not root_np:
|
||||
return 0
|
||||
|
||||
cleared = 0
|
||||
stack = [root_np]
|
||||
while stack:
|
||||
node = stack.pop()
|
||||
if not node:
|
||||
continue
|
||||
try:
|
||||
if node.is_empty():
|
||||
continue
|
||||
except Exception:
|
||||
try:
|
||||
if node.isEmpty():
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
try:
|
||||
tag_names = list(node.get_tag_keys())
|
||||
except Exception:
|
||||
try:
|
||||
tag_names = list(node.getTagKeys())
|
||||
except Exception:
|
||||
tag_names = []
|
||||
|
||||
for tag_name in tag_names:
|
||||
try:
|
||||
node.clearTag(tag_name)
|
||||
cleared += 1
|
||||
except Exception:
|
||||
try:
|
||||
node.setTag(tag_name, "")
|
||||
cleared += 1
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
try:
|
||||
children = list(node.get_children())
|
||||
except Exception:
|
||||
try:
|
||||
children = list(node.getChildren())
|
||||
except Exception:
|
||||
children = []
|
||||
for child in reversed(children):
|
||||
stack.append(child)
|
||||
|
||||
return cleared
|
||||
|
||||
def build_virtual_hierarchy(self):
|
||||
"""Build a readonly virtual tree from node_list path keys."""
|
||||
root = {
|
||||
|
||||
@ -68,6 +68,7 @@ class SSBOEditor:
|
||||
self.source_model = None
|
||||
self.source_model_root = None
|
||||
self._source_child_base_mats = {}
|
||||
self._source_transform_baseline_tag = "_ssbo_source_base_local_mat"
|
||||
self.last_import_tree_key = None
|
||||
self.last_import_root_name = None
|
||||
self.ssbo = None
|
||||
@ -547,6 +548,52 @@ class SSBOEditor:
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
def _set_source_node_transform_baseline(self, node):
|
||||
"""Attach the imported local transform baseline onto one source node."""
|
||||
if not self._node_is_valid(node):
|
||||
return False
|
||||
local_mat = self._get_node_local_mat_copy(node)
|
||||
if local_mat is None:
|
||||
return False
|
||||
try:
|
||||
node.setPythonTag(self._source_transform_baseline_tag, LMatrix4f(local_mat))
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _get_source_node_transform_baseline(self, node):
|
||||
"""Read the imported local transform baseline for one source node."""
|
||||
if not self._node_is_valid(node):
|
||||
return None
|
||||
try:
|
||||
baseline_mat = node.getPythonTag(self._source_transform_baseline_tag)
|
||||
except Exception:
|
||||
baseline_mat = None
|
||||
if baseline_mat is None:
|
||||
return None
|
||||
try:
|
||||
return LMatrix4f(baseline_mat)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _capture_source_transform_baselines(self, root_node=None):
|
||||
"""Snapshot imported local transforms so dirty tags can be normalized later."""
|
||||
start_node = root_node if self._node_is_valid(root_node) else self.source_model_root
|
||||
if not self._node_is_valid(start_node):
|
||||
return 0
|
||||
|
||||
captured = 0
|
||||
stack = [start_node]
|
||||
while stack:
|
||||
node = stack.pop()
|
||||
if self._set_source_node_transform_baseline(node):
|
||||
captured += 1
|
||||
children = list(self._iter_children(node))
|
||||
for child in reversed(children):
|
||||
if self._node_is_valid(child):
|
||||
stack.append(child)
|
||||
return captured
|
||||
|
||||
def _get_top_level_group_keys(self):
|
||||
if not self.controller or not getattr(self.controller, "tree_root_key", None):
|
||||
return []
|
||||
@ -636,15 +683,101 @@ class SSBOEditor:
|
||||
continue
|
||||
composed_mat = LMatrix4f(base_mat)
|
||||
composed_mat *= model_root_mat
|
||||
self._apply_source_local_mat_if_changed(source_child, composed_mat)
|
||||
|
||||
def _get_node_local_mat_copy(self, node):
|
||||
"""Return a defensive copy of a node's current local matrix."""
|
||||
if not self._node_is_valid(node):
|
||||
return None
|
||||
try:
|
||||
return LMatrix4f(node.get_mat())
|
||||
except Exception:
|
||||
try:
|
||||
source_child.set_mat(composed_mat)
|
||||
source_child.setTag("scene_transform_dirty", "true")
|
||||
return LMatrix4f(node.getMat())
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _apply_source_local_mat_if_changed(self, source_node, local_mat):
|
||||
"""Write a local matrix back only when it materially differs."""
|
||||
if not self._node_is_valid(source_node) or local_mat is None:
|
||||
return False
|
||||
|
||||
current_local_mat = self._get_node_local_mat_copy(source_node)
|
||||
dirty_tag_changed = self._update_source_transform_dirty_tag(source_node, local_mat)
|
||||
if current_local_mat is not None and self._matrices_close(current_local_mat, local_mat):
|
||||
return dirty_tag_changed
|
||||
|
||||
try:
|
||||
source_node.set_mat(local_mat)
|
||||
except Exception:
|
||||
try:
|
||||
source_node.setMat(local_mat)
|
||||
except Exception:
|
||||
return False
|
||||
dirty_tag_changed = self._update_source_transform_dirty_tag(source_node, local_mat) or dirty_tag_changed
|
||||
return True
|
||||
|
||||
def _update_source_transform_dirty_tag(self, source_node, local_mat):
|
||||
"""Keep scene_transform_dirty aligned with the imported baseline."""
|
||||
if not self._node_is_valid(source_node) or local_mat is None:
|
||||
return False
|
||||
|
||||
baseline_local_mat = self._get_source_node_transform_baseline(source_node)
|
||||
if baseline_local_mat is None:
|
||||
return False
|
||||
|
||||
try:
|
||||
has_dirty_tag = bool(source_node.hasTag("scene_transform_dirty"))
|
||||
except Exception:
|
||||
has_dirty_tag = False
|
||||
|
||||
is_dirty = not self._matrices_close(local_mat, baseline_local_mat)
|
||||
if is_dirty:
|
||||
if has_dirty_tag:
|
||||
try:
|
||||
source_child.setMat(composed_mat)
|
||||
source_child.setTag("scene_transform_dirty", "true")
|
||||
if str(source_node.getTag("scene_transform_dirty") or "").lower() == "true":
|
||||
return False
|
||||
except Exception:
|
||||
continue
|
||||
pass
|
||||
try:
|
||||
source_node.setTag("scene_transform_dirty", "true")
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
if not has_dirty_tag:
|
||||
return False
|
||||
try:
|
||||
source_node.clearTag("scene_transform_dirty")
|
||||
return True
|
||||
except Exception:
|
||||
try:
|
||||
source_node.setTag("scene_transform_dirty", "")
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def normalize_source_transform_dirty_tags(self, root_node=None):
|
||||
"""Normalize transform dirty tags against import-time baselines."""
|
||||
start_node = root_node if self._node_is_valid(root_node) else self.source_model_root
|
||||
if not self._node_is_valid(start_node):
|
||||
return 0
|
||||
|
||||
normalized = 0
|
||||
stack = [start_node]
|
||||
while stack:
|
||||
node = stack.pop()
|
||||
local_mat = self._get_node_local_mat_copy(node)
|
||||
if local_mat is not None and self._update_source_transform_dirty_tag(node, local_mat):
|
||||
normalized += 1
|
||||
children = list(self._iter_children(node))
|
||||
for child in reversed(children):
|
||||
if self._node_is_valid(child):
|
||||
stack.append(child)
|
||||
|
||||
if normalized:
|
||||
print(f"[SSBOEditor] Normalized transform dirty tags: {normalized}")
|
||||
return normalized
|
||||
|
||||
def _snapshot_runtime_node_transform_to_source_node(self, scene_node, source_node):
|
||||
"""Persist one runtime node transform back to its matching source-tree node."""
|
||||
@ -689,16 +822,7 @@ class SSBOEditor:
|
||||
return False
|
||||
|
||||
local_mat = current_net_mat * inv_parent_mat
|
||||
try:
|
||||
source_node.set_mat(local_mat)
|
||||
source_node.setTag("scene_transform_dirty", "true")
|
||||
except Exception:
|
||||
try:
|
||||
source_node.setMat(local_mat)
|
||||
source_node.setTag("scene_transform_dirty", "true")
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
return self._apply_source_local_mat_if_changed(source_node, local_mat)
|
||||
|
||||
def _snapshot_runtime_object_transforms_to_source_root(self):
|
||||
"""
|
||||
@ -1616,7 +1740,11 @@ class SSBOEditor:
|
||||
self._set_node_name(imported_root, unique_root_name)
|
||||
if not skip_heavy_material_processing:
|
||||
self._detach_shared_materials_in_subtree(imported_root)
|
||||
self._capture_source_transform_baselines(imported_root)
|
||||
imported_roots = [imported_root]
|
||||
else:
|
||||
for imported_root in imported_roots:
|
||||
self._capture_source_transform_baselines(imported_root)
|
||||
|
||||
self.source_model = source_root
|
||||
self._restore_saved_material_bindings_from_tags(source_root)
|
||||
@ -1633,6 +1761,7 @@ class SSBOEditor:
|
||||
self._set_node_name(imported_root, unique_root_name)
|
||||
if not skip_heavy_material_processing:
|
||||
self._detach_shared_materials_in_subtree(imported_root)
|
||||
self._capture_source_transform_baselines(imported_root)
|
||||
|
||||
if keep_source_model and not append:
|
||||
self.source_model = imported_root
|
||||
@ -2959,6 +3088,23 @@ class SSBOEditor:
|
||||
|
||||
rebuilt = 0
|
||||
for chunk_id in sorted(chunks):
|
||||
chunk = chunks.get(chunk_id)
|
||||
if not isinstance(chunk, dict):
|
||||
continue
|
||||
|
||||
needs_rebuild = bool(chunk.get("dynamic_enabled") or chunk.get("dirty"))
|
||||
static_np = chunk.get("static_np")
|
||||
if not needs_rebuild:
|
||||
try:
|
||||
needs_rebuild = not (static_np and not static_np.is_empty())
|
||||
except Exception:
|
||||
try:
|
||||
needs_rebuild = not (static_np and not static_np.isEmpty())
|
||||
except Exception:
|
||||
needs_rebuild = True
|
||||
if not needs_rebuild:
|
||||
continue
|
||||
|
||||
try:
|
||||
controller._rebuild_static_chunk(chunk_id)
|
||||
controller._set_chunk_dynamic(chunk_id, False)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user