Fix SSBO picking sync and deletion cleanup
This commit is contained in:
parent
1fd7e1d7ac
commit
756db5b010
@ -215,7 +215,7 @@ class ObjectController:
|
||||
# Build hierarchy metadata first so UI can mirror source model tree.
|
||||
self._build_scene_tree(model)
|
||||
|
||||
root_name = (model.get_name() or "scene") + "_hybrid"
|
||||
root_name = model.get_name() or "scene"
|
||||
scene_root = NodePath(root_name)
|
||||
pick_root = NodePath(root_name + "_pick")
|
||||
self.model = scene_root
|
||||
|
||||
@ -77,6 +77,7 @@ class SSBOEditor:
|
||||
self.keys = {}
|
||||
self.pick_mask = BitMask32.bit(29)
|
||||
self.pick_buffer = None
|
||||
self._empty_pick_scene = NodePath("ssbo_pick_empty")
|
||||
|
||||
# Initialize ImGui Backend if not already present
|
||||
if not hasattr(self.base, 'imgui_backend'):
|
||||
@ -272,8 +273,9 @@ class SSBOEditor:
|
||||
frag_src = f.read().replace('\r', '')
|
||||
pick_shader = Shader.make(Shader.SL_GLSL, vert_src, frag_src)
|
||||
pick_scene = getattr(self.controller, "pick_model", None) or self.model
|
||||
pick_scene.show(self.pick_mask)
|
||||
self.pick_cam.set_scene(pick_scene)
|
||||
if pick_scene and not pick_scene.is_empty():
|
||||
pick_scene.show(self.pick_mask)
|
||||
self.pick_cam.set_scene(pick_scene or self._empty_pick_scene)
|
||||
initial_state = NodePath("initial")
|
||||
initial_state.set_shader(pick_shader, 100)
|
||||
# Remove global SSBO input, Chunks have their own inputs
|
||||
@ -288,8 +290,36 @@ class SSBOEditor:
|
||||
self.pick_buffer.set_clear_color(Vec4(0, 0, 0, 0))
|
||||
self.pick_buffer.set_clear_color_active(True)
|
||||
|
||||
def _is_model_attached(self):
|
||||
"""Whether the SSBO render root is still attached to scene graph."""
|
||||
if not self.model or self.model.is_empty():
|
||||
return False
|
||||
parent = self.model.get_parent()
|
||||
return bool(parent) and not parent.is_empty()
|
||||
|
||||
def _sync_pick_scene_binding(self):
|
||||
"""Switch pick camera scene based on current model attachment state."""
|
||||
if not hasattr(self, "pick_cam") or not self.pick_cam:
|
||||
return
|
||||
|
||||
if self._is_model_attached() and self.controller:
|
||||
target_scene = getattr(self.controller, "pick_model", None) or self.model
|
||||
else:
|
||||
target_scene = self._empty_pick_scene
|
||||
|
||||
if not target_scene:
|
||||
target_scene = self._empty_pick_scene
|
||||
|
||||
if self.pick_cam.get_scene() != target_scene:
|
||||
self.pick_cam.set_scene(target_scene)
|
||||
|
||||
def pick_object(self, mx, my):
|
||||
if not self.pick_buffer: return
|
||||
self._sync_pick_scene_binding()
|
||||
if not self._is_model_attached():
|
||||
if self.selected_ids or getattr(self, "_group_proxy", None):
|
||||
self.clear_selection()
|
||||
return
|
||||
|
||||
self.pick_lens.set_fov(0.1)
|
||||
self.pick_lens.set_film_offset(0, 0)
|
||||
@ -359,17 +389,54 @@ class SSBOEditor:
|
||||
self._sync_pick_transforms()
|
||||
return task.cont
|
||||
|
||||
def _matrices_close(self, a, b, eps=1e-5):
|
||||
"""Small helper for robust matrix change detection."""
|
||||
for r in range(4):
|
||||
ra = a.get_row(r)
|
||||
rb = b.get_row(r)
|
||||
if (abs(ra[0] - rb[0]) > eps or
|
||||
abs(ra[1] - rb[1]) > eps or
|
||||
abs(ra[2] - rb[2]) > eps or
|
||||
abs(ra[3] - rb[3]) > eps):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _sync_pick_root_transform(self):
|
||||
"""
|
||||
Keep pick root aligned with the render root transform.
|
||||
This covers transforms applied to the whole imported model
|
||||
(for example, moving box.glb from scene hierarchy).
|
||||
"""
|
||||
if not self.controller or not self.model or not self._is_model_attached():
|
||||
return
|
||||
|
||||
pick_root = getattr(self.controller, "pick_model", None)
|
||||
if not pick_root:
|
||||
return
|
||||
|
||||
if self.model.is_empty() or pick_root.is_empty():
|
||||
return
|
||||
|
||||
pick_root.set_mat(self.base.render, self.model.get_mat(self.base.render))
|
||||
|
||||
def _sync_pick_transforms(self):
|
||||
"""Sync pick model transforms to match render model transforms."""
|
||||
if not self.controller:
|
||||
return
|
||||
self._sync_pick_root_transform()
|
||||
for gid in self.selected_ids:
|
||||
obj_np = self.controller.id_to_object_np.get(gid)
|
||||
pick_np = self.controller.id_to_pick_np.get(gid)
|
||||
if obj_np and pick_np and not obj_np.is_empty() and not pick_np.is_empty():
|
||||
# pick_np is direct child of pick_root (identity transform),
|
||||
# so local mat = world-space mat.
|
||||
pick_np.set_mat(obj_np.get_mat(self.base.render))
|
||||
obj_world_mat = obj_np.get_mat(self.base.render)
|
||||
pick_world_mat = pick_np.get_mat(self.base.render)
|
||||
if not self._matrices_close(obj_world_mat, pick_world_mat):
|
||||
# Sync by world transform so this stays correct even when
|
||||
# the model root itself has been moved in scene hierarchy.
|
||||
pick_np.set_mat(self.base.render, obj_world_mat)
|
||||
chunk_id = self.controller.id_to_chunk.get(gid)
|
||||
if chunk_id is not None and chunk_id in self.controller.chunks:
|
||||
self.controller.chunks[chunk_id]["dirty"] = True
|
||||
|
||||
def clear_selection(self):
|
||||
self._stop_pick_sync_task()
|
||||
@ -381,6 +448,15 @@ class SSBOEditor:
|
||||
if self._transform_gizmo:
|
||||
self._transform_gizmo.detach()
|
||||
|
||||
def on_model_deleted(self, deleted_node):
|
||||
"""Called by app deletion flow when SSBO root model is deleted."""
|
||||
if not deleted_node or deleted_node.is_empty() or not self.model:
|
||||
return
|
||||
if deleted_node != self.model:
|
||||
return
|
||||
self.clear_selection()
|
||||
self._sync_pick_scene_binding()
|
||||
|
||||
def _cleanup_group_proxy(self):
|
||||
"""Reparent objects back to their chunk and remove the group proxy."""
|
||||
proxy = getattr(self, '_group_proxy', None)
|
||||
@ -392,7 +468,7 @@ class SSBOEditor:
|
||||
obj_np = self.controller.id_to_object_np.get(gid)
|
||||
pick_np = self.controller.id_to_pick_np.get(gid)
|
||||
if obj_np and pick_np and not obj_np.is_empty() and not pick_np.is_empty():
|
||||
pick_np.set_mat(obj_np.get_mat(self.base.render))
|
||||
pick_np.set_mat(self.base.render, obj_np.get_mat(self.base.render))
|
||||
chunk_id = self.controller.id_to_chunk.get(gid)
|
||||
if chunk_id is not None and chunk_id in self.controller.chunks:
|
||||
self.controller.chunks[chunk_id]["dirty"] = True
|
||||
@ -559,6 +635,9 @@ class SSBOEditor:
|
||||
def update_task(self, task):
|
||||
dt = globalClock.getDt()
|
||||
io = imgui.get_io()
|
||||
self._sync_pick_scene_binding()
|
||||
# Scene-hierarchy transforms may move the whole SSBO model root; keep pick root in sync.
|
||||
self._sync_pick_root_transform()
|
||||
|
||||
if io.want_capture_keyboard: return task.cont
|
||||
|
||||
|
||||
@ -735,6 +735,9 @@ class AppActions:
|
||||
|
||||
node_name = node.getName() or "未命名节点"
|
||||
parent = node.getParent()
|
||||
ssbo_editor = getattr(self, "ssbo_editor", None)
|
||||
ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None
|
||||
deleting_ssbo_root = bool(ssbo_model and (node == ssbo_model))
|
||||
|
||||
# 创建删除命令
|
||||
if hasattr(self, 'command_manager') and self.command_manager:
|
||||
@ -747,6 +750,12 @@ class AppActions:
|
||||
print(f"[删除] 命令管理器不可用,直接删除节点: {node_name}")
|
||||
self._perform_node_cleanup(node)
|
||||
node.removeNode()
|
||||
|
||||
if deleting_ssbo_root and ssbo_editor:
|
||||
try:
|
||||
ssbo_editor.on_model_deleted(node)
|
||||
except Exception as e:
|
||||
print(f"[SSBO] 删除模型后清理失败: {e}")
|
||||
|
||||
print(f"[删除] 成功删除节点: {node_name}")
|
||||
return True
|
||||
|
||||
Loading…
Reference in New Issue
Block a user