from panda3d.core import NodePath from direct.showbase.DirectObject import DirectObject from direct.interval.IntervalGlobal import Sequence, Func, Wait from imgui_bundle import imgui, imgui_ctx class SceneGraphExplorer(DirectObject): def __init__(self, nodePath = None, active = True): DirectObject.__init__(self) self.nodePath: NodePath = nodePath self.active = active if not nodePath: self.nodePath = base.render self.__currentNodePath = self.nodePath self.windowPos = None self.reparentTarget: NodePath | None = None self.flashOnClick = False self.highlightSeq: Sequence | None = None self.rename = '' self.focusSetNameInput = False self.__firstDraw = True self.accept('imgui-new-frame', self.__draw) def flash(self, nodePath): if self.highlightSeq: self.highlightSeq.finish() colorScale = nodePath.getColorScale() self.highlightSeq = Sequence( Func(nodePath.setColorScale, (1.0, 0.0, 0.0, 0.0)), Wait(1), Func(nodePath.setColorScale, colorScale) ) self.highlightSeq.start() def __del__(self): self.ignoreAll() def __draw(self): if not self.active: return id = 0 def drawTreeForNode(nodePath): nonlocal id flags = 0 if nodePath.getNumChildren() == 0: flags = imgui.TreeNodeFlags_.leaf.value elif nodePath == self.nodePath: flags = imgui.TreeNodeFlags_.default_open.value typeName = nodePath.node().getType().getName() name = nodePath.getName() tree = imgui.tree_node_ex(f"{self.nodePath.getName()}-{id}", flags, f"{typeName} {name}") if self.flashOnClick and imgui.is_item_clicked(): self.flash(nodePath) namePopupId = imgui.get_id(f"{nodePath}-namePopup") if imgui.begin_popup_context_item(): clickedFlash, _ = imgui.menu_item("Flash", "", False) if clickedFlash: self.flash(nodePath) imgui.separator() clickedSetName, _ = imgui.menu_item("Set Name", "", False) if clickedSetName: self.rename = nodePath.getName() self.focusSetNameInput = True imgui.open_popup(namePopupId) imgui.separator() clickedSetTarget, _ = imgui.menu_item("Set Reparent Target", "", False) if clickedSetTarget: self.reparentTarget = nodePath clickedReparent, _ = imgui.menu_item("Reparent to Target", "", False, self.reparentTarget is not None) if clickedReparent: nodePath.reparentTo(self.reparentTarget) clickedWrtReparent, _ = imgui.menu_item("WRT Reparent To Target", "", False, self.reparentTarget is not None) if clickedWrtReparent: nodePath.wrtReparentTo(self.reparentTarget) imgui.separator() clickedPlace, _ = imgui.menu_item("Place", "", False) if clickedPlace: nodePath.place() if nodePath != self.nodePath: clickedExplore, _ = imgui.menu_item("Explore Seperately", "", False) if clickedExplore: nodePath.explore() clickedDelete, _ = imgui.menu_item("Delete", "", False) if clickedDelete: nodePath.removeNode() imgui.end_popup() with imgui_ctx.begin_popup(f"{nodePath}-namePopup") as namePopup: if namePopup: imgui.text("Set Name:") imgui.same_line() if self.focusSetNameInput: imgui.set_keyboard_focus_here() self.focusSetNameInput = False nameChanged, newName = imgui.input_text("##input", self.rename, imgui.InputTextFlags_.chars_no_blank.value) if nameChanged: self.rename = newName if imgui.button("OK") or imgui.is_key_pressed(imgui.Key.enter): nodePath.setName(self.rename) self.rename = '' imgui.close_current_popup() imgui.same_line() if imgui.button("Cancel") or imgui.is_key_pressed(imgui.Key.escape): self.rename = '' imgui.close_current_popup() if tree: for child in nodePath.children: id += 1 drawTreeForNode(child) imgui.tree_pop() if self.__firstDraw: imgui.set_next_window_size((410,761)) self.__firstDraw = False if self.windowPos and (self.nodePath != self.__currentNodePath): imgui.set_next_window_pos(self.windowPos) self.__currentNodePath = self.nodePath with imgui_ctx.begin(f"Explore: {self.nodePath.getName()}", True, imgui.WindowFlags_.menu_bar) as (_, windowOpen): if not windowOpen or not self.nodePath: self.active = False return self.windowPos = imgui.get_window_pos() with imgui_ctx.begin_menu_bar() as menuBar: if menuBar: with imgui_ctx.begin_menu("Options") as optionsMenu: if optionsMenu: clickedFlashToggle, _ = imgui.menu_item("Flash on Click", "", self.flashOnClick) if clickedFlashToggle: self.flashOnClick = not self.flashOnClick imgui.text(f"Active Reparent Target: {self.reparentTarget}") drawTreeForNode(self.nodePath)