From 939ea35913d7d57a788ce40f37cee6cbae088afe Mon Sep 17 00:00:00 2001 From: Hector <145347438+hudomn@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:49:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8Eweb=E6=89=93=E5=8C=85=E5=90=88?= =?UTF-8?q?=E5=B9=B6=EF=BC=8C=E4=BF=AE=E5=A4=8D=E4=BF=9D=E5=AD=98=E5=90=8E?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E7=9A=84=E4=BD=8D=E7=BD=AE=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E8=BF=98=E5=8E=9F=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ssbo_component/ssbo_editor.py | 53 ++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/ssbo_component/ssbo_editor.py b/ssbo_component/ssbo_editor.py index e18f63f7..082c8489 100644 --- a/ssbo_component/ssbo_editor.py +++ b/ssbo_component/ssbo_editor.py @@ -735,7 +735,8 @@ class SSBOEditor: return False try: - source_node.set_mat(self.source_model_root, current_net_mat) + reference_node = self._get_transform_snapshot_reference_node(source_node) + source_node.set_mat(reference_node, current_net_mat) source_node.setTag("scene_transform_dirty", "true") try: self._cache_top_level_source_child_base_mat(source_node, source_node.get_mat()) @@ -747,7 +748,7 @@ class SSBOEditor: return True except Exception: try: - source_node.setMat(self.source_model_root, current_net_mat) + source_node.setMat(reference_node, current_net_mat) source_node.setTag("scene_transform_dirty", "true") try: self._cache_top_level_source_child_base_mat(source_node, source_node.get_mat()) @@ -807,7 +808,8 @@ class SSBOEditor: # Use robust Panda3D set_mat(ref, mat) to handle all coordinate conversions. # This ensures the source node's net transform relative to the source root # exactly matches the runtime node's net transform relative to self.model. - source_node.set_mat(self.source_model_root, current_net_mat) + reference_node = self._get_transform_snapshot_reference_node(source_node) + source_node.set_mat(reference_node, current_net_mat) source_node.setTag("scene_transform_dirty", "true") # Update top-level child baseline cache for structural sync @@ -1093,7 +1095,8 @@ class SSBOEditor: try: # Corrected: use Panda3D native world-to-local projection relative to source root - source_node.set_mat(self.source_model_root, current_net_mat) + reference_node = self._get_transform_snapshot_reference_node(source_node) + source_node.set_mat(reference_node, current_net_mat) source_node.setTag("scene_transform_dirty", "true") try: self._cache_top_level_source_child_base_mat(source_node, source_node.get_mat()) @@ -1132,6 +1135,48 @@ class SSBOEditor: return None return node + def _is_node_in_subtree(self, node, subtree_root): + if not self._node_is_valid(node) or not self._node_is_valid(subtree_root): + return False + current = node + while self._node_is_valid(current): + if current == subtree_root: + return True + try: + current = current.get_parent() + except Exception: + try: + current = current.getParent() + except Exception: + return False + return False + + def _get_transform_snapshot_reference_node(self, source_node): + """ + Pick the correct reference node when snapshotting runtime transforms. + + In the single-top-level-model path, the runtime moves that only top-level + child transform onto self.model. Descendant runtime matrices are then + relative to the sole source child, not to source_model_root. Saving those + descendants against source_model_root bakes the inverse root transform into + children, so reopening restores children but loses the whole-model pose. + """ + source_root = self.source_model_root + if not self._node_is_valid(source_node) or not self._node_is_valid(source_root): + return source_root + + source_children = self._get_source_root_children() + if len(source_children) != 1 or not self._node_is_valid(source_children[0]): + return source_root + + sole_source_child = source_children[0] + if source_node == sole_source_child: + return source_root + + if self._is_node_in_subtree(source_node, sole_source_child): + return sole_source_child + return source_root + def _resolve_tree_key_for_source_node(self, node): """Resolve a source-tree NodePath back to the controller tree key.""" source_root = self._ensure_source_model_root()