From 283c891554e1fff5e2034dc060ea0b216eb85392 Mon Sep 17 00:00:00 2001 From: Hector <145347438+hudomn@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:02:09 +0800 Subject: [PATCH] 0407 --- project/project_manager.py | 161 +++++++++++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 8 deletions(-) diff --git a/project/project_manager.py b/project/project_manager.py index 61344698..13d12abc 100644 --- a/project/project_manager.py +++ b/project/project_manager.py @@ -1189,15 +1189,31 @@ class ProjectManager: except Exception: pass ssbo_loaded = self._load_scene_description_via_ssbo(scene_description, project_path, asset_database) + use_manual_asset_import = ( + not ssbo_loaded + and callable(getattr(self.world, "_import_model_with_menu_logic", None)) + ) - scene_root, keep_nodes = self._build_scene_root_from_description( + scene_root, keep_nodes, built_nodes = self._build_scene_root_from_description( scene_description, project_path, asset_database, include_mode="all", - skip_asset_nodes=ssbo_loaded, + skip_asset_nodes=(ssbo_loaded or use_manual_asset_import), + return_node_map=True, ) + manually_imported_models = [] + if use_manual_asset_import: + manually_imported_models = self._load_scene_assets_via_manual_import( + scene_description, + project_path, + asset_database, + scene_root, + keep_nodes, + built_nodes, + ) + scene_manager = getattr(self.world, "scene_manager", None) if scene_manager: scene_manager.models = [] @@ -1212,6 +1228,13 @@ class ProjectManager: except Exception: root_children = [] + manual_model_keys = set() + for manual_model in manually_imported_models: + try: + manual_model_keys.add(id(manual_model)) + except Exception: + continue + try: ssbo_editor = getattr(self.world, "ssbo_editor", None) runtime_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None @@ -1233,9 +1256,9 @@ class ProjectManager: if child.hasTag("is_model_root") or child.hasTag("asset_guid"): built_model_nodes.append(child) try: - if scene_manager and hasattr(scene_manager, "setupCollision"): + if scene_manager and hasattr(scene_manager, "setupCollision") and id(child) not in manual_model_keys: scene_manager.setupCollision(child) - if scene_manager and hasattr(scene_manager, "_processModelAnimations"): + if scene_manager and hasattr(scene_manager, "_processModelAnimations") and id(child) not in manual_model_keys: scene_manager._processModelAnimations(child) except Exception: pass @@ -1248,7 +1271,14 @@ class ProjectManager: if scene_manager: if not ssbo_loaded: - scene_manager.models = built_model_nodes + merged_models = [] + for candidate in list(manually_imported_models) + built_model_nodes: + if not candidate or candidate.isEmpty(): + continue + if any(existing == candidate for existing in merged_models): + continue + merged_models.append(candidate) + scene_manager.models = merged_models else: merged_models = list(getattr(scene_manager, "models", []) or []) for child in built_model_nodes: @@ -1352,6 +1382,83 @@ class ProjectManager: top_level_nodes.append(node) return top_level_nodes, node_lookup + def _load_scene_assets_via_manual_import( + self, + scene_description, + project_path, + asset_database, + scene_root, + keep_nodes, + built_nodes, + ): + import_with_menu = getattr(self.world, "_import_model_with_menu_logic", None) + if not callable(import_with_menu): + return [] + + top_level_nodes, node_lookup = self._iter_top_level_scene_asset_nodes(scene_description) + imported_models = [] + for node in top_level_nodes: + components = dict(node.get("components", {}) or {}) + model_component = dict(components.get("model", {}) or {}) + imported_node_key = str( + model_component.get("imported_node_key", "") + or node.get("imported_node_key", "") + or "" + ).strip() + if imported_node_key: + return [] + + asset_guid = str( + model_component.get("asset_guid", "") + or node.get("asset_guid", "") + or "" + ).strip() + if not asset_guid: + continue + + asset_record = asset_database.get_asset(asset_guid) + if not asset_record: + return [] + + asset_rel = str( + asset_record.get("asset_path", "") + or model_component.get("asset_path", "") + or node.get("asset_path", "") + or "" + ).strip() + if not asset_rel: + return [] + + asset_abs = os.path.join(project_path, asset_rel.replace("/", os.sep)) + if not os.path.exists(asset_abs): + return [] + + imported_np = import_with_menu( + asset_abs, + select_model=False, + set_origin=False, + show_info_message=False, + show_success_message=False, + ) + if not imported_np or imported_np.isEmpty(): + return [] + + parent_np = scene_root + parent_id = self._resolve_scene_description_parent(node, keep_nodes, node_lookup) + if parent_id in built_nodes: + parent_np = built_nodes[parent_id] + imported_np.reparentTo(parent_np) + self._apply_scene_description_state_to_subtree( + imported_np, + node, + project_path, + node_lookup, + apply_material_state=True, + prune_missing_children=False, + ) + imported_models.append(imported_np) + return imported_models + def _apply_scene_description_state_to_node(self, target_np, node, project_path, apply_material_state=True): if not target_np or target_np.isEmpty() or not isinstance(node, dict): return @@ -1447,9 +1554,17 @@ class ProjectManager: target_np.setTag("has_scripts", "true") target_np.setTag("scripts_info", json.dumps(scripts, ensure_ascii=False)) - if apply_material_state: + if apply_material_state and self._should_restore_saved_material_state(node): self._apply_saved_material_tags_to_node(target_np) + def _should_restore_saved_material_state(self, node): + if not isinstance(node, dict): + return False + tags = dict(node.get("tags", {}) or {}) + if str(tags.get("scene_material_dirty", "") or "").strip().lower() == "true": + return True + return False + def _apply_saved_material_tags_to_node(self, target_np): """Rebuild runtime material state from serialized material_* tags.""" if not target_np or target_np.isEmpty(): @@ -1590,7 +1705,19 @@ class ProjectManager: runtime_children = [] try: - runtime_children = [child for child in target_np.getChildren() if child and not child.isEmpty()] + for child in target_np.getChildren(): + if not child or child.isEmpty(): + continue + child_name = "" + try: + child_name = str(child.getName() or "") + except Exception: + child_name = "" + if child_name.startswith("modelCollision_"): + continue + if child_name.startswith("selectionOutline"): + continue + runtime_children.append(child) except Exception: runtime_children = [] @@ -1619,10 +1746,17 @@ class ProjectManager: return "" runtime_children_by_key = {} + runtime_children_by_name = {} for runtime_index, runtime_child in enumerate(runtime_children): key = _runtime_imported_key(runtime_child) if key and key not in runtime_children_by_key: runtime_children_by_key[key] = (runtime_index, runtime_child) + try: + runtime_name = str(runtime_child.getName() or "").strip() + except Exception: + runtime_name = "" + if runtime_name: + runtime_children_by_name.setdefault(runtime_name, []).append((runtime_index, runtime_child)) used_runtime_indices = set() for child_entry in child_nodes: @@ -1638,6 +1772,15 @@ class ProjectManager: target_runtime_index = keyed_index target_runtime_child = keyed_child + if target_runtime_child is None: + entry_name = str(child_entry.get("name", "") or "").strip() + for named_index, named_child in runtime_children_by_name.get(entry_name, []): + if named_index in used_runtime_indices: + continue + target_runtime_index = named_index + target_runtime_child = named_child + break + if target_runtime_child is None: child_index = _node_index(child_entry) if child_index < 0 or child_index >= len(runtime_children): @@ -2148,7 +2291,7 @@ class ProjectManager: parent_id = parent_node.get("parent_id") return False - def _build_scene_root_from_description(self, scene_description, project_path, asset_database, include_mode="all", skip_asset_nodes=False): + def _build_scene_root_from_description(self, scene_description, project_path, asset_database, include_mode="all", skip_asset_nodes=False, return_node_map=False): nodes = list(scene_description.get("nodes", []) or []) node_lookup = { str(node.get("node_id", "") or ""): dict(node) @@ -2200,6 +2343,8 @@ class ProjectManager: ) if rebuilt_np: built_nodes[node_id] = rebuilt_np + if return_node_map: + return scene_root, keep_nodes, built_nodes return scene_root, keep_nodes def saveProject(self):