fix: unify py311 startup and restore LUI editor panel toggle

This commit is contained in:
ayuan9957 2026-03-04 12:39:02 +08:00
parent 718cf802be
commit c5dbc6be6f
13 changed files with 1275 additions and 6 deletions

View File

@ -1,9 +1,48 @@
import os import os
import shutil
import subprocess
import sys import sys
def _maybe_relaunch_with_py311():
"""Use Python 3.11 to match ui/lui.pyd ABI on Windows."""
if sys.version_info >= (3, 11):
return
if os.environ.get("EG_RELAUNCHED_PY311") == "1":
return
if os.name != "nt":
return
py_launcher = shutil.which("py")
if not py_launcher:
print(f"⚠ 当前解释器是 Python {sys.version.split()[0]},未找到 py launcher无法自动切换到 3.11。")
return
try:
probe = subprocess.run(
[py_launcher, "-3.11", "-c", "import sys;print(sys.executable)"],
capture_output=True,
text=True,
check=False,
)
if probe.returncode != 0:
print("⚠ 未检测到可用的 Python 3.11LUI 可能不可用。")
return
except Exception as e:
print(f"⚠ 检测 Python 3.11 失败: {e}")
return
os.environ["EG_RELAUNCHED_PY311"] = "1"
relaunch_cmd = [py_launcher, "-3.11", os.path.abspath(__file__), *sys.argv[1:]]
print(f"✓ 检测到 Python {sys.version.split()[0]},自动切换到 Python 3.11: {probe.stdout.strip()}")
os.execv(py_launcher, relaunch_cmd)
# 添加项目根目录到 Python 路径 # 添加项目根目录到 Python 路径
project_root = os.path.dirname(os.path.abspath(__file__)) project_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, project_root) sys.path.insert(0, project_root)
third_party_path = os.path.join(project_root, "third_party")
if os.path.isdir(third_party_path):
sys.path.insert(0, third_party_path)
# 设置工作目录为项目根目录 # 设置工作目录为项目根目录
os.chdir(project_root) os.chdir(project_root)
@ -22,6 +61,7 @@ sys.path.insert(0, icons_path)
# 现在可以导入并运行主程序 # 现在可以导入并运行主程序
if __name__ == "__main__": if __name__ == "__main__":
_maybe_relaunch_with_py311()
args = sys.argv[1:] args = sys.argv[1:]
# args = "/home/tiger/桌面/Test1" # args = "/home/tiger/桌面/Test1"
# args = "C:/Users/29381/Desktop/1" # args = "C:/Users/29381/Desktop/1"

35
main.py
View File

@ -1,3 +1,33 @@
import os
import shutil
import subprocess
import sys
import warnings
from pathlib import Path
if __name__ == "__main__" and os.name == "nt" and sys.version_info < (3, 11):
py_launcher = shutil.which("py")
if py_launcher and os.environ.get("EG_RELAUNCHED_PY311") != "1":
try:
probe = subprocess.run(
[py_launcher, "-3.11", "-c", "import sys;print(sys.executable)"],
capture_output=True,
text=True,
check=False,
)
if probe.returncode == 0:
os.environ["EG_RELAUNCHED_PY311"] = "1"
os.execv(py_launcher, [py_launcher, "-3.11", os.path.abspath(__file__), *sys.argv[1:]])
else:
print("⚠ 未检测到可用的 Python 3.11LUI 可能不可用。")
except Exception as relaunch_error:
print(f"⚠ 自动切换 Python 3.11 失败: {relaunch_error}")
PROJECT_ROOT = Path(__file__).resolve().parent
THIRD_PARTY_DIR = PROJECT_ROOT / "third_party"
if str(THIRD_PARTY_DIR) not in sys.path:
sys.path.insert(0, str(THIRD_PARTY_DIR))
from panda3d.core import loadPrcFileData, WindowProperties, Point3 from panda3d.core import loadPrcFileData, WindowProperties, Point3
from math import pi, sin, cos from math import pi, sin, cos
@ -11,11 +41,6 @@ import p3dimgui
from imgui_bundle import imgui, imgui_ctx from imgui_bundle import imgui, imgui_ctx
import sys
import os
import warnings
from pathlib import Path
# 导入MyWorld类和必要的模块 # 导入MyWorld类和必要的模块
from core.world import CoreWorld from core.world import CoreWorld
from core.selection import SelectionSystem from core.selection import SelectionSystem

33
third_party/p3dimgui/__init__.py vendored Normal file
View File

@ -0,0 +1,33 @@
from .backend import ImGuiBackend
from .utilities.PlaceManager import PlaceManager
from .utilities.PlacePanel import PlacePanel
from .utilities.ExplorerManager import ExplorerManager
from .utilities.SceneGraphExplorer import SceneGraphExplorer
from .utilities.TimeSliderManager import TimeSliderManager
from .utilities.IntervalTimeSlider import IntervalTimeSlider
__all__ = ['init',
'ImGuiBackend',
"PlacePanel",
'PlaceManager',
'ExplorerManager',
'SceneGraphExplorer',
'TimeSliderManager',
'IntervalTimeSlider',
]
def init(window = None, parent = None,
style = 'dark', wantPlaceManager = True,
wantExplorerManager = True, wantTimeSliderManager = True):
try:
base.imgui
except AttributeError:
base.imgui = ImGuiBackend(window, parent, style)
if wantPlaceManager:
base.placeManager = PlaceManager()
if wantExplorerManager:
base.explorerManager = ExplorerManager()
if wantTimeSliderManager:
base.timeSliderManager = TimeSliderManager()

565
third_party/p3dimgui/backend.py vendored Normal file
View File

@ -0,0 +1,565 @@
from panda3d.core import (
ButtonHandle,
ButtonRegistry,
ColorAttrib,
ColorBlendAttrib,
CullFaceAttrib,
DepthTestAttrib,
Geom,
GeomNode,
GeomTriangles,
GeomVertexData,
GeomVertexArrayFormat,
GeomVertexFormat,
InternalName,
KeyboardButton,
MouseButton,
NodePath,
RenderState,
SamplerState,
Shader,
ScissorAttrib,
Texture,
TextureAttrib
)
from direct.directnotify import DirectNotifyGlobal
from direct.showbase.DirectObject import DirectObject
try:
from shaders import *
except ModuleNotFoundError:
from .shaders import *
from imgui_bundle import imgui
import ctypes
import pyperclip
import sys
__all__ = ['ImGuiBackend', 'ImGuiStyles']
KEYBOARD_BUTTON_TO_IMGUI_KEY = {
# Special keys
KeyboardButton.tab(): imgui.Key.tab.value,
KeyboardButton.left(): imgui.Key.left_arrow.value,
KeyboardButton.right(): imgui.Key.right_arrow.value,
KeyboardButton.up(): imgui.Key.up_arrow.value,
KeyboardButton.down(): imgui.Key.down_arrow.value,
KeyboardButton.enter(): imgui.Key.enter.value,
KeyboardButton.escape(): imgui.Key.escape.value,
KeyboardButton.backspace(): imgui.Key.backspace.value,
KeyboardButton.space(): imgui.Key.space.value,
KeyboardButton.lshift(): imgui.Key.left_shift.value,
KeyboardButton.rshift(): imgui.Key.right_shift.value,
KeyboardButton.shift(): imgui.Key.mod_shift.value,
KeyboardButton.control(): imgui.Key.mod_ctrl.value,
KeyboardButton.lcontrol(): imgui.Key.left_ctrl.value,
KeyboardButton.rcontrol(): imgui.Key.right_ctrl.value,
KeyboardButton.alt(): imgui.Key.mod_alt.value,
KeyboardButton.lalt(): imgui.Key.left_alt.value,
KeyboardButton.ralt(): imgui.Key.right_alt.value,
KeyboardButton._del(): imgui.Key.delete.value,
KeyboardButton.home(): imgui.Key.home.value,
KeyboardButton.end(): imgui.Key.end.value,
KeyboardButton.pageUp(): imgui.Key.page_up.value,
KeyboardButton.pageDown(): imgui.Key.page_down.value,
KeyboardButton.insert(): imgui.Key.insert.value,
KeyboardButton.capsLock(): imgui.Key.caps_lock.value,
KeyboardButton.numLock(): imgui.Key.num_lock.value,
KeyboardButton.scrollLock(): imgui.Key.scroll_lock.value,
KeyboardButton.menu(): imgui.Key.menu.value,
KeyboardButton.lmeta(): imgui.Key.left_super.value,
KeyboardButton.rmeta(): imgui.Key.right_super.value,
KeyboardButton.meta(): imgui.Key.mod_super.value,
# Function keys (F1 to F16)
KeyboardButton.f1(): imgui.Key.f1.value,
KeyboardButton.f2(): imgui.Key.f2.value,
KeyboardButton.f3(): imgui.Key.f3.value,
KeyboardButton.f4(): imgui.Key.f4.value,
KeyboardButton.f5(): imgui.Key.f5.value,
KeyboardButton.f6(): imgui.Key.f6.value,
KeyboardButton.f7(): imgui.Key.f7.value,
KeyboardButton.f8(): imgui.Key.f8.value,
KeyboardButton.f9(): imgui.Key.f9.value,
KeyboardButton.f10(): imgui.Key.f10.value,
KeyboardButton.f11(): imgui.Key.f11.value,
KeyboardButton.f12(): imgui.Key.f12.value,
KeyboardButton.f13(): imgui.Key.f13.value,
KeyboardButton.f14(): imgui.Key.f14.value,
KeyboardButton.f15(): imgui.Key.f15.value,
KeyboardButton.f16(): imgui.Key.f16.value,
# Numeric keys (0-9) on the main keyboard
KeyboardButton.asciiKey("0"): imgui.Key._0.value,
KeyboardButton.asciiKey("1"): imgui.Key._1.value,
KeyboardButton.asciiKey("2"): imgui.Key._2.value,
KeyboardButton.asciiKey("3"): imgui.Key._3.value,
KeyboardButton.asciiKey("4"): imgui.Key._4.value,
KeyboardButton.asciiKey("5"): imgui.Key._5.value,
KeyboardButton.asciiKey("6"): imgui.Key._6.value,
KeyboardButton.asciiKey("7"): imgui.Key._7.value,
KeyboardButton.asciiKey("8"): imgui.Key._8.value,
KeyboardButton.asciiKey("9"): imgui.Key._9.value,
# Alphabetic keys
KeyboardButton.asciiKey("a"): imgui.Key.a.value,
KeyboardButton.asciiKey("b"): imgui.Key.b.value,
KeyboardButton.asciiKey("c"): imgui.Key.c.value,
KeyboardButton.asciiKey("d"): imgui.Key.d.value,
KeyboardButton.asciiKey("e"): imgui.Key.e.value,
KeyboardButton.asciiKey("f"): imgui.Key.f.value,
KeyboardButton.asciiKey("g"): imgui.Key.g.value,
KeyboardButton.asciiKey("h"): imgui.Key.h.value,
KeyboardButton.asciiKey("i"): imgui.Key.i.value,
KeyboardButton.asciiKey("j"): imgui.Key.j.value,
KeyboardButton.asciiKey("k"): imgui.Key.k.value,
KeyboardButton.asciiKey("l"): imgui.Key.l.value,
KeyboardButton.asciiKey("m"): imgui.Key.m.value,
KeyboardButton.asciiKey("n"): imgui.Key.n.value,
KeyboardButton.asciiKey("o"): imgui.Key.o.value,
KeyboardButton.asciiKey("p"): imgui.Key.p.value,
KeyboardButton.asciiKey("q"): imgui.Key.q.value,
KeyboardButton.asciiKey("r"): imgui.Key.r.value,
KeyboardButton.asciiKey("s"): imgui.Key.s.value,
KeyboardButton.asciiKey("t"): imgui.Key.t.value,
KeyboardButton.asciiKey("u"): imgui.Key.u.value,
KeyboardButton.asciiKey("v"): imgui.Key.v.value,
KeyboardButton.asciiKey("w"): imgui.Key.w.value,
KeyboardButton.asciiKey("x"): imgui.Key.x.value,
KeyboardButton.asciiKey("y"): imgui.Key.y.value,
KeyboardButton.asciiKey("z"): imgui.Key.z.value,
# Punctuation keys
KeyboardButton.asciiKey("!"): imgui.Key._1.value,
KeyboardButton.asciiKey("@"): imgui.Key._2.value,
KeyboardButton.asciiKey("#"): imgui.Key._3.value,
KeyboardButton.asciiKey("$"): imgui.Key._4.value,
KeyboardButton.asciiKey("%"): imgui.Key._5.value,
KeyboardButton.asciiKey("^"): imgui.Key._6.value,
KeyboardButton.asciiKey("&"): imgui.Key._7.value,
KeyboardButton.asciiKey("*"): imgui.Key._8.value,
KeyboardButton.asciiKey("("): imgui.Key._9.value,
KeyboardButton.asciiKey(")"): imgui.Key._0.value,
KeyboardButton.asciiKey("-"): imgui.Key.minus.value,
KeyboardButton.asciiKey("="): imgui.Key.equal.value,
KeyboardButton.asciiKey("["): imgui.Key.left_bracket.value,
KeyboardButton.asciiKey("]"): imgui.Key.right_bracket.value,
KeyboardButton.asciiKey("\\"): imgui.Key.backslash.value,
KeyboardButton.asciiKey(";"): imgui.Key.semicolon.value,
KeyboardButton.asciiKey("'"): imgui.Key.apostrophe.value,
KeyboardButton.asciiKey(","): imgui.Key.comma.value,
KeyboardButton.asciiKey("."): imgui.Key.period.value,
KeyboardButton.asciiKey("/"): imgui.Key.slash.value,
# Special characters
KeyboardButton.asciiKey("`"): imgui.Key.grave_accent.value,
KeyboardButton.asciiKey("~"): imgui.Key.grave_accent.value,
KeyboardButton.asciiKey("_"): imgui.Key.minus.value,
KeyboardButton.asciiKey("+"): imgui.Key.equal.value,
KeyboardButton.asciiKey("<"): imgui.Key.comma.value,
KeyboardButton.asciiKey(">"): imgui.Key.period.value,
KeyboardButton.asciiKey("?"): imgui.Key.slash.value,
# Additional modifier keys
KeyboardButton.printScreen(): imgui.Key.print_screen.value,
KeyboardButton.pause(): imgui.Key.pause.value,
}
class GeomList:
def __init__(self, vdata: GeomVertexData):
self.vdata: GeomVertexData = vdata # vertex data shared among the below GeomNodes
self.nodepaths: list[NodePath] = []
class ImGuiBackend(DirectObject):
"""
The main Dear ImGui backend code for Panda3D.
(Based on https://github.com/bluekyu/panda3d_imgui; ported to
Python using imgui-bundle and updated to support Dear ImGui version 1.92.3)
"""
def __init__(self, window = None, parent = None, style = 'dark'):
DirectObject.__init__(self)
self.notify = DirectNotifyGlobal.directNotify.newCategory("imgui")
if not window:
window = base.win
if not parent:
parent = base.pixel2d
self.window = window
self.root = parent.attachNewNode('imgui-root', 1000)
self.context = imgui.create_context()
self.io = imgui.get_io()
# Add backend flag for rendering textures
self.io.backend_flags |= imgui.BackendFlags_.renderer_has_textures.value
self.vformat = None
self.textureCounter = 0
self.textures: dict[int, Texture] = {}
self.geomData: list[GeomList] = []
self.__setupStyle(style)
self.__setupGeom()
self.__setupShader()
self.__setupFront()
self.__setupEvent()
self.__windowEvent()
self.__setupButton()
self.__setupRender()
# Set Clipboard functions
imgui.get_platform_io().platform_set_clipboard_text_fn = self.__setClipboardText
imgui.get_platform_io().platform_get_clipboard_text_fn = self.__getClipboardText
def toggle(self):
if self.root.isHidden():
self.root.show()
else:
self.root.hide()
def hide(self):
self.root.hide()
def show(self):
self.root.show()
def isMouseCaptured(self) -> bool:
# This returns True if the mouse is over
# a Imgui window or any other reason.
return self.io.want_capture_mouse
def isKeyboardCaptured(self) -> bool:
# This returns True when a Imgui text
# input widget is focused or any other reason.
return self.io.want_capture_keyboard
def __setupStyle(self, style):
match style:
case 'dark':
imgui.style_colors_dark()
case 'classic':
imgui.style_colors_classic()
case 'light':
imgui.style_colors_light()
case _:
self.notify.warning(f"Unknown style: \"{style}\"")
def __onButton(self, keyName: str, down: bool):
# Panda3D adds the prefix of the modifier keys to the key name
# if they are held down, so we have to strip them out.
if keyName.startswith(('control-', 'alt-', 'shift-', 'shift-control-', 'shift-alt-', 'shift-control-alt-',
'meta-', 'control-meta-', 'alt-meta-', 'control-alt-meta-', 'shift-meta-',
'shift-control-meta', 'shift-alt-meta-', 'shift-control-alt-meta-')):
keyName = keyName.split('-')[-1]
if keyName == '':
# must be minus.
keyName = '-'
button = ButtonRegistry.ptr().getButton(keyName)
if button == ButtonHandle.none():
return
if MouseButton.isMouseButton(button):
if button == MouseButton.one():
self.io.add_mouse_button_event(0, down)
elif button == MouseButton.three():
# NOTE: In Panda3D, MouseButton.three() is the right mouse button
# while MouseButton.two() is the middle (scroll wheel) button
self.io.add_mouse_button_event(1, down)
elif button == MouseButton.two():
self.io.add_mouse_button_event(2, down)
elif button == MouseButton.four():
self.io.add_mouse_button_event(3, down)
elif button == MouseButton.five():
self.io.add_mouse_button_event(4, down)
elif button == MouseButton.wheelUp() and down:
self.io.add_mouse_wheel_event(0, .5)
elif button == MouseButton.wheelDown() and down:
self.io.add_mouse_wheel_event(0, -.5)
elif button == MouseButton.wheelLeft() and down:
self.io.add_mouse_wheel_event(.5, 0)
elif button == MouseButton.wheelRight() and down:
self.io.add_mouse_wheel_event(-.5, 0)
else:
imguiKey = KEYBOARD_BUTTON_TO_IMGUI_KEY.get(button, imgui.Key.none.value)
self.io.add_key_event(imguiKey, down)
def __onKeystroke(self, keyName):
# NOTE: Panda3D for some reason doesn't recognize if
# the caps lock is on for macOS. You would have to
# hold down shift to input capital letters.
# Windows/Linux handles this just fine.
button = ButtonRegistry.ptr().getButton(keyName)
if keyName == ' ':
# There is no space button on the ButtonRegistry.
button = KeyboardButton.space()
if button.hasAsciiEquivalent():
self.io.add_input_character(ord(button.getAsciiEquivalent()))
def __setupGeom(self):
self.notify.debug("__setupGeom")
arrayFormat = GeomVertexArrayFormat(
InternalName.getVertex(), 4, Geom.NT_stdfloat, Geom.C_point,
InternalName.getColor(), 1, Geom.NT_packed_dabc, Geom.C_color
)
self.vformat = GeomVertexFormat.registerFormat(GeomVertexFormat(arrayFormat))
self.root.setState(RenderState.make(
ColorAttrib.makeVertex(),
ColorBlendAttrib.make(ColorBlendAttrib.M_add, ColorBlendAttrib.O_incoming_alpha, ColorBlendAttrib.O_one_minus_incoming_alpha),
DepthTestAttrib.make(DepthTestAttrib.M_none),
CullFaceAttrib.make(CullFaceAttrib.M_cull_none)
))
def __setupShader(self):
self.notify.debug("__setupShader")
shader = Shader.make(
Shader.SL_GLSL,
VERT_SHADER,
FRAG_SHADER,
)
self.root.setShader(shader)
def __setupFront(self):
self.notify.debug("__setupFront")
self.io.fonts.add_font_default()
def __setupEvent(self):
self.notify.debug("__setupEvent")
self.accept("window-event", self.__windowEvent)
def __windowEvent(self, _ = None):
if self.window:
self.io.display_size = (self.window.getXSize(), self.window.getYSize())
def __setupButton(self):
self.notify.debug("__setupButton")
base.buttonThrowers[0].node().setButtonDownEvent('buttonDown')
base.buttonThrowers[0].node().setButtonUpEvent('buttonUp')
base.buttonThrowers[0].node().setKeystrokeEvent('keystroke')
def __buttonDown(keyName):
self.__onButton(keyName, True)
def __buttonUp(keyName):
self.__onButton(keyName, False)
def __keyStroke(keyName):
self.__onKeystroke(keyName)
def __handleOobe():
if base.bboard.get('oobeEnabled'):
self.ignore('buttonDown')
self.ignore('buttonUp')
self.accept('oobe-down', __buttonDown)
self.accept('oobe-up', __buttonUp)
else:
self.ignore('oobe-down')
self.ignore('oobe-up')
# Set the names to the button events back.
base.buttonThrowers[0].node().setButtonDownEvent('buttonDown')
base.buttonThrowers[0].node().setButtonUpEvent('buttonUp')
self.accept('buttonDown', __buttonDown)
self.accept('buttonUp', __buttonUp)
self.accept('buttonDown', __buttonDown)
self.accept('buttonUp', __buttonUp)
self.accept('keystroke', __keyStroke)
self.accept(base.bboard.getEvent('oobeEnabled'), __handleOobe)
# self.addTask(__handleOobe, appendTask=True)
def __setupRender(self):
self.notify.debug("__setupRender")
# NOTE: igLoop (frame rendering) has 50 sort.
base.taskMgr.add(self.__newFrame, "imgui-new-frame", 0)
base.taskMgr.add(self.__renderFrame, "imgui-render-frame", 40)
def __newFrame(self, task):
if self.root.isHidden():
return task.cont
self.io.delta_time = base.clock.getDt()
if self.window:
mouse = self.window.getPointer(0)
if mouse.getInWindow():
if self.io.want_set_mouse_pos:
self.window.movePointer(0, self.io.mouse_pos.x, self.io.mouse_pos.y)
else:
self.io.mouse_pos = mouse.getX(), mouse.getY()
else:
self.io.mouse_pos = (-imgui.FLT_MAX, -imgui.FLT_MAX)
imgui.new_frame()
base.messenger.send("imgui-new-frame")
return task.cont
def __renderFrame(self, task):
if self.root.isHidden():
return task.cont
imgui.render()
fbWidth = self.io.display_size.x * self.io.display_framebuffer_scale.x
fbHeight = self.io.display_size.y * self.io.display_framebuffer_scale.y
self.__updateTextures()
drawData = imgui.get_draw_data()
for child in self.root.children:
child.detachNode()
for i, cmdList in enumerate(drawData.cmd_lists):
if i > len(self.geomData) - 1:
self.geomData.append(GeomList(GeomVertexData(f"imgui-vertex-{i}", self.vformat, Geom.UH_stream)))
geomList = self.geomData[i]
vertexHandle = geomList.vdata.modifyArrayHandle(0)
if vertexHandle.getNumRows() < cmdList.vtx_buffer.size():
vertexHandle.uncleanSetNumRows(cmdList.vtx_buffer.size())
vertexHandle.setData(ctypes.string_at(cmdList.vtx_buffer.data_address(), cmdList.vtx_buffer.size() * imgui.VERTEX_SIZE))
indexBuffer = cmdList.idx_buffer.data_address()
for k, drawCmd, in enumerate(cmdList.cmd_buffer):
if k > len(geomList.nodepaths) - 1:
geomList.nodepaths.append(self.__createGeomnode(geomList.vdata))
np = geomList.nodepaths[k]
np.reparentTo(self.root)
node = np.node()
indexHandle = node.modifyGeom(0).modifyPrimitive(0).modifyVertices(drawCmd.elem_count).modifyHandle()
if indexHandle.getNumRows() < drawCmd.elem_count:
indexHandle.uncleanSetNumRows(drawCmd.elem_count)
indexHandle.setData(ctypes.string_at(indexBuffer, drawCmd.elem_count * imgui.INDEX_SIZE))
indexBuffer += (drawCmd.elem_count * imgui.INDEX_SIZE)
# FIXME: This works, but it breaks Modal windows as the white
# covers the window (but still usable) instead of the background.
# (Not sure if this is to blame or the shader...)
state = RenderState.make(ScissorAttrib.make(
drawCmd.clip_rect.x / fbWidth,
drawCmd.clip_rect.z / fbWidth,
1 - drawCmd.clip_rect.w / fbHeight,
1 - drawCmd.clip_rect.y / fbHeight
))
if drawCmd.tex_ref.get_tex_id():
texture = self.textures[drawCmd.tex_ref.get_tex_id()]
state = state.addAttrib(TextureAttrib.make(texture))
node.setGeomState(0, state)
return task.cont
def loadTexture(self, texture: Texture | str) -> imgui.ImTextureRef:
if type(texture) == str:
texture = base.loader.loadTexture(texture)
self.notify.debug(f"loadTexture ({self.textureCounter + 1})")
self.textureCounter += 1
self.textures[self.textureCounter] = texture
return imgui.ImTextureRef(self.textureCounter)
def removeTexture(self, tex: imgui.ImTextureRef, unload = True):
texture = self.textures.get(tex.get_tex_id(), None)
if texture:
self.notify.debug(f"removeTexture ({tex.get_tex_id()})")
if unload:
texture.clear()
base.loader.unloadTexture(texture)
del texture
del self.textures[tex.get_tex_id()]
def __updateTextures(self):
for tex in imgui.get_platform_io().textures:
if tex.status != imgui.ImTextureStatus.ok:
self.__updateTexture(tex)
def __updateTexture(self, tex: imgui.ImTextureData):
def __setPixelsToTexture(texture: Texture):
# The easiest way to update a Panda texture
# is to recreate the ram image and rewrite
# all the pixels.
# This clears the RamImage if one exists already.
textureData = texture.makeRamImage()
pixels = tex.get_pixels_array()
textureData.setData(pixels.tobytes())
match tex.status:
case imgui.ImTextureStatus.want_create:
self.notify.debug(f"__updateTexture ({self.textureCounter + 1}): create ({tex.width}x{tex.height})")
assert tex.tex_id == 0
assert tex.backend_user_data is None
assert tex.format == imgui.ImTextureFormat.rgba32
texture = Texture()
texture.setName(f"imgui-texture-{tex.unique_id}")
texture.setup2dTexture(tex.width, tex.height, Texture.T_unsigned_byte, Texture.F_rgba32)
texture.setMinfilter(SamplerState.FT_linear)
texture.setMagfilter(SamplerState.FT_linear)
__setPixelsToTexture(texture)
self.textureCounter += 1
self.textures[self.textureCounter] = texture
tex.tex_id = self.textureCounter
tex.status = imgui.ImTextureStatus.ok
case imgui.ImTextureStatus.want_updates:
self.notify.debug(f"__updateTexture ({tex.tex_id}): update ({tex.width}x{tex.height})")
__setPixelsToTexture(self.textures[tex.tex_id])
tex.status = imgui.ImTextureStatus.ok
case imgui.ImTextureStatus.want_destroy:
texture = self.textures.get(tex.tex_id, None)
if texture:
texture.clear()
del texture
del self.textures[tex.tex_id]
tex.tex_id = 0
tex.status = imgui.ImTextureStatus.destroyed
@staticmethod
def __createGeomnode(vdata: GeomVertexData) -> NodePath:
prim = GeomTriangles(Geom.UH_stream)
if imgui.INDEX_SIZE == 2:
prim.setIndexType(Geom.NT_uint16)
else:
prim.setIndexType(Geom.NT_uint32)
prim.closePrimitive()
geom = Geom(vdata)
geom.addPrimitive(prim)
node = GeomNode("imgui-geom")
node.addGeom(geom, RenderState.makeEmpty())
return NodePath(node)
def cleanup(self):
if self.context:
imgui.destroy_context()
self.context = None
@staticmethod
def __setClipboardText(_, str: str):
pyperclip.copy(str)
@staticmethod
def __getClipboardText(_):
return pyperclip.paste()

37
third_party/p3dimgui/shaders.py vendored Normal file
View File

@ -0,0 +1,37 @@
# These shaders is in GLSL version 1.20 is because so they can work
# right out of the box in macOS without having to force the OpenGL version
# to 3.2 (gl-version 3 2).
FRAG_SHADER = """
#version 120
varying vec2 texcoord;
varying vec4 color;
uniform sampler2D p3d_Texture0;
void main()
{
gl_FragColor = color * texture2D(p3d_Texture0, texcoord);
}
"""
VERT_SHADER = """
#version 120
attribute vec4 p3d_Vertex; // { vec2 pos, vec2 uv }
attribute vec4 p3d_Color;
varying vec2 texcoord;
varying vec4 color;
uniform mat4 p3d_ModelViewProjectionMatrix;
void main()
{
texcoord = p3d_Vertex.zw;
color = p3d_Color.bgra;
gl_Position = p3d_ModelViewProjectionMatrix * vec4(p3d_Vertex.x, 0.0, -p3d_Vertex.y, 1.0);
}
"""

View File

@ -0,0 +1,26 @@
from direct.showbase.DirectObject import DirectObject
from panda3d.core import NodePath
from p3dimgui.utilities.SceneGraphExplorer import SceneGraphExplorer
from direct.extensions_native.extension_native_helpers import Dtool_funcToMethod
class ExplorerManager(DirectObject):
def __init__(self):
DirectObject.__init__(self)
self.nodesToExplorers: dict[NodePath, SceneGraphExplorer] = {}
# Replace the explore() extension method for NodePath
def explore(nodePath):
self.nodesToExplorers[nodePath] = SceneGraphExplorer(nodePath)
Dtool_funcToMethod(explore, NodePath)
self.accept('imgui-new-frame', self.__checkActive)
def __checkActive(self):
for node in list(self.nodesToExplorers.keys()):
placer = self.nodesToExplorers[node]
if not placer.active:
del self.nodesToExplorers[node]

View File

@ -0,0 +1,88 @@
from imgui_bundle import imgui, imgui_ctx
from direct.showbase.DirectObject import DirectObject
class IntervalTimeSlider(DirectObject):
def __init__(self, interval, active=True):
DirectObject.__init__(self)
self.interval = interval
self.__currentInterval = interval
self.active = active
self.windowPos = None
self.__firstDraw = True
self.accept('imgui-new-frame', self.__draw)
def __draw(self):
if not self.active:
return
curDuration = self.interval.getT()
maxDuration = self.interval.getDuration()
if self.windowPos and (self.interval != self.__currentInterval):
imgui.set_next_window_pos(self.windowPos)
self.__currentInterval = self.interval
if self.__firstDraw:
imgui.set_next_window_size((745,76))
self.__firstDraw = False
with imgui_ctx.begin(f"Time Slider \"{self.interval.getName()}\"", True,
imgui.WindowFlags_.no_resize.value | imgui.WindowFlags_.no_scrollbar.value | imgui.WindowFlags_.no_scroll_with_mouse.value) as (_, windowOpen):
if not windowOpen or not self.interval:
self.active = False
return
self.windowPos = imgui.get_window_pos()
imgui.push_item_width(732)
changed, value = imgui.slider_float(
"##slider", curDuration,
v_min=0.0, v_max=self.interval.getDuration(),
format=f"{self.__calculateTimeFormat(curDuration)}/{self.__calculateTimeFormat(maxDuration)}",
flags=imgui.SliderFlags_.no_input.value
)
imgui.pop_item_width()
if changed:
self.interval.setT(value)
imgui.spacing()
imgui.same_line(spacing=312)
if imgui.button("<<"):
self.interval.setT(0)
self.interval.pause()
imgui.same_line()
if imgui.button("Pause" if self.interval.isPlaying() else "Play"):
if self.interval.isPlaying():
self.interval.pause()
else:
self.interval.resume()
imgui.same_line()
if imgui.button(">>"):
self.interval.setT(self.interval.getDuration())
self.interval.pause()
def __calculateTimeFormat(self, duration):
if duration >= 60:
self.currentTime = "00:00"
totalSeconds = int(duration) % 60
totalMinutes = int((duration - totalSeconds) / 60)
if totalMinutes < 10:
totalMinutes = "%s%s" % (0, totalMinutes)
if totalSeconds < 10:
totalSeconds = "%s%s" % (0, totalSeconds)
return "%s:%s" % (totalMinutes, totalSeconds)
else:
if duration < 10:
return "00:0%s" % (int(duration))
else:
return "00:%s" % (int(duration))

View File

@ -0,0 +1,27 @@
from direct.showbase.DirectObject import DirectObject
from panda3d.core import NodePath
from p3dimgui.utilities.PlacePanel import PlacePanel
from direct.extensions_native.extension_native_helpers import Dtool_funcToMethod
class PlaceManager(DirectObject):
def __init__(self):
DirectObject.__init__(self)
self.nodesToPlacers: dict[NodePath, PlacePanel] = {}
# Replace the place() and rgbPanel() extension methods for NodePath
def place(nodePath):
self.nodesToPlacers[nodePath] = PlacePanel(nodePath)
Dtool_funcToMethod(place, NodePath)
Dtool_funcToMethod(place, NodePath, 'rgbPanel')
self.accept('imgui-new-frame', self.__checkActive)
def __checkActive(self):
for node in list(self.nodesToPlacers.keys()):
placer = self.nodesToPlacers[node]
if not placer.active:
del self.nodesToPlacers[node]

View File

@ -0,0 +1,224 @@
from direct.showbase.DirectObject import DirectObject
from panda3d.core import NodePath
from imgui_bundle import imgui, imgui_ctx
class PlacePanel(DirectObject):
def __init__(self, nodePath: NodePath, active=True):
DirectObject.__init__(self)
self.nodePath: NodePath = nodePath
self.__currentNodePath: NodePath = nodePath
self.active = active
self.scaleUniform = True
scale = self.nodePath.getScale()
if scale[0] != scale[1] or \
scale[0] != scale[2] or \
scale[1] != scale[2]:
self.scaleUniform = False
self.windowPos = None
self.accept('imgui-new-frame', self.__draw)
def __del__(self):
self.ignoreAll()
def __draw(self):
if not self.active:
return
if not self.nodePath:
# Must've gotten deleted.
self.active = False
return
pos = self.nodePath.getPos()
hpr = self.nodePath.getHpr()
scale = self.nodePath.getScale()
# This is done to silence the "has no color set" warnings.
color = (1.0, 1.0, 1.0, 1.0)
if self.nodePath.hasColor():
color = self.nodePath.getColor()
colorScale = (1.0, 1.0, 1.0, 1.0)
if self.nodePath.hasColorScale():
colorScale = self.nodePath.getColorScale()
if self.windowPos and (self.nodePath != self.__currentNodePath):
imgui.set_next_window_pos(self.windowPos)
self.__currentNodePath = self.nodePath
with imgui_ctx.begin(f"Node Placer \"{self.nodePath.getName()}\"", True) as (_, windowOpen):
if not windowOpen:
# Close has been clicked.
self.active = False
return
self.windowPos = imgui.get_window_pos()
with imgui_ctx.begin_group():
imgui.text("Position")
imgui.push_item_width(50)
xChanged, xValue = imgui.drag_float("X", pos[0], v_speed=0.05, format='%.3f')
yChanged, yValue = imgui.drag_float("Y", pos[1], v_speed=0.05, format='%.3f')
zChanged, zValue = imgui.drag_float("Z", pos[2], v_speed=0.05, format='%.3f')
imgui.pop_item_width()
if xChanged:
self.nodePath.setX(xValue)
elif yChanged:
self.nodePath.setY(yValue)
elif zChanged:
self.nodePath.setZ(zValue)
imgui.same_line(spacing=20)
with imgui_ctx.begin_group():
imgui.text("Orientation")
imgui.push_item_width(50)
hChanged, hValue = imgui.drag_float("H", hpr[0], v_speed=0.05, format='%.3f')
pChanged, pValue = imgui.drag_float("P", hpr[1], v_speed=0.05, format='%.3f')
rChanged, rValue = imgui.drag_float("R", hpr[2], v_speed=0.05, format='%.3f')
imgui.pop_item_width()
if hChanged:
self.nodePath.setH(hValue)
elif pChanged:
self.nodePath.setP(pValue)
elif rChanged:
self.nodePath.setR(rValue)
imgui.same_line(spacing=20)
with imgui_ctx.begin_group():
imgui.text("Scaling")
imgui.push_item_width(50)
sxChanged, sxValue = imgui.drag_float("X##scale", scale[0], v_speed=0.05, format='%.3f')
syChanged, syValue = imgui.drag_float("Y##scale", scale[1], v_speed=0.05, format='%.3f')
szChanged, szValue = imgui.drag_float("Z##scale", scale[2], v_speed=0.05, format='%.3f')
uniformBoxClicked, _ = imgui.checkbox("Uniform Scaling", self.scaleUniform)
imgui.pop_item_width()
if uniformBoxClicked:
self.scaleUniform = not self.scaleUniform
if sxChanged:
if self.scaleUniform:
self.nodePath.setScale(sxValue)
else:
self.nodePath.setSx(sxValue)
elif syChanged:
if self.scaleUniform:
self.nodePath.setScale(syValue)
else:
self.nodePath.setSy(syValue)
elif szChanged:
if self.scaleUniform:
self.nodePath.setScale(szValue)
else:
self.nodePath.setSz(szValue)
# Colors
colorChanged, newColor = imgui.color_edit4('Color', color, imgui.ColorEditFlags_.float.value)
if colorChanged:
self.nodePath.setColor(*newColor)
colorScaleChanged, newColorScale = imgui.color_edit4('Color Scale', colorScale, imgui.ColorEditFlags_.float.value)
if colorScaleChanged:
self.nodePath.setColorScale(*newColorScale)
imgui.separator()
# Copy buttons
if imgui.button("Copy setPosHprScale()"):
imgui.open_popup("posHprScaleMenu")
with imgui_ctx.begin_popup("posHprScaleMenu") as posHprScaleMenuOpened:
if posHprScaleMenuOpened:
for string in (
f".setPosHpr(({pos[0]:.2f}, {pos[1]:.2f}, {pos[2]:.2f}), ({hpr[0]:.2f}, {hpr[1]:.2f}, {hpr[2]:.2f}))",
f".setPosHprScale(({pos[0]:.2f}, {pos[1]:.2f}, {pos[2]:.2f}), ({hpr[0]:.2f}, {hpr[1]:.2f}, {hpr[2]:.2f}), ({scale[0]:.2f}, {scale[1]:.2f}, {scale[2]:.2f}))",
"",
f".setPos(({pos[0]:.2f}, {pos[1]:.2f}, {pos[2]:.2f}))",
f".setHpr(({hpr[0]:.2f}, {hpr[1]:.2f}, {hpr[2]:.2f}))",
f".setScale(({scale[0]:.2f}, {scale[1]:.2f}, {scale[2]:.2f}))",
"",
f".setX({pos[0]:.2f})",
f".setY({pos[1]:.2f})",
f".setZ({pos[2]:.2f})",
"",
f".setH({hpr[0]:.2f})",
f".setP({hpr[1]:.2f})",
f".setR({hpr[2]:.2f})",
"",
f".setSx({scale[0]:.2f})",
f".setSy({scale[1]:.2f})",
f".setSz({scale[2]:.2f})",
"",
f".posHprInterval(1, ({pos[0]:.2f}, {pos[1]:.2f}, {pos[2]:.2f}), ({hpr[0]:.2f}, {hpr[1]:.2f}, {hpr[2]:.2f}), blendType='easeInOut'),",
f".scaleInterval(1, ({scale[0]:.2f}, {scale[1]:.2f}, {scale[2]:.2f}), blendType='easeInOut'),",
f".posHprScaleInterval(1, ({pos[0]:.2f}, {pos[1]:.2f}, {pos[2]:.2f}), ({hpr[0]:.2f}, {hpr[1]:.2f}, {hpr[2]:.2f}), ({scale[0]:.2f}, {scale[1]:.2f}, {scale[2]:.2f}), blendType='easeInOut'),",
"",
f"Func(node.setScale, ({scale[0]:.2f}, {scale[1]:.2f}, {scale[2]:.2f})),",
f"Func(node.setPosHpr, ({pos[0]:.2f}, {pos[1]:.2f}, {pos[2]:.2f}), ({hpr[0]:.2f}, {hpr[1]:.2f}, {hpr[2]:.2f})),",
f"Func(node.setPosHprScale, ({pos[0]:.2f}, {pos[1]:.2f}, {pos[2]:.2f}), ({hpr[0]:.2f}, {hpr[1]:.2f}, {hpr[2]:.2f}), ({scale[0]:.2f}, {scale[1]:.2f}, {scale[2]:.2f})),",
):
if not string:
imgui.separator()
continue
clicked, _ = imgui.menu_item(string, "", False)
if clicked:
imgui.set_clipboard_text(string)
imgui.close_current_popup()
imgui.same_line(spacing=20)
if imgui.button("Copy setColor()"):
imgui.open_popup("colorMenu")
with imgui_ctx.begin_popup("colorMenu") as colorMenuOpened:
if colorMenuOpened:
for string in (
f".setColor(({colorScale[0]:.2f}, {color[1]:.2f}, {color[2]:.2f}))",
"",
f".colorInterval(1, ({color[0]:.2f}, {color[1]:.2f}, {color[2]:.2f}), blendType='easeInOut'),",
"",
f"Func(node.setColor, ({color[0]:.2f}, {color[1]:.2f}, {color[2]:.2f})),"
):
if not string:
imgui.separator()
continue
clicked, _ = imgui.menu_item(string, "", False)
if clicked:
imgui.set_clipboard_text(string)
imgui.close_current_popup()
imgui.same_line(spacing=20)
if imgui.button("Copy setColorScale()"):
imgui.open_popup("colorScaleMenu")
with imgui_ctx.begin_popup("colorScaleMenu") as colorScaleMenuOpened:
if colorScaleMenuOpened:
for string in (
f".setColorScale(({colorScale[0]:.2f}, {colorScale[1]:.2f}, {colorScale[2]:.2f}))",
"",
f".colorScaleInterval(1, ({colorScale[0]:.2f}, {colorScale[1]:.2f}, {colorScale[2]:.2f}), blendType='easeInOut'),",
"",
f"Func(node.setColorScale, ({colorScale[0]:.2f}, {colorScale[1]:.2f}, {colorScale[2]:.2f})),"
):
if not string:
imgui.separator()
continue
clicked, _ = imgui.menu_item(string, "", False)
if clicked:
imgui.set_clipboard_text(string)
imgui.close_current_popup()

View File

@ -0,0 +1,171 @@
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)

View File

@ -0,0 +1,26 @@
from direct.showbase.DirectObject import DirectObject
from panda3d.direct import CInterval
from p3dimgui.utilities.IntervalTimeSlider import IntervalTimeSlider
from direct.extensions_native.extension_native_helpers import Dtool_funcToMethod
class TimeSliderManager(DirectObject):
def __init__(self):
DirectObject.__init__(self)
self.intervalToTimeSliders: dict[CInterval, IntervalTimeSlider] = {}
# Replace the popupControls() extension method for CInterval
def popupControls(interval):
self.intervalToTimeSliders[interval] = IntervalTimeSlider(interval)
Dtool_funcToMethod(popupControls, CInterval)
Dtool_funcToMethod(popupControls, CInterval, "slider")
self.accept('imgui-new-frame', self.__checkActive)
def __checkActive(self):
for node in list(self.intervalToTimeSliders.keys()):
placer = self.intervalToTimeSliders[node]
if not placer.active:
del self.intervalToTimeSliders[node]

View File

@ -614,12 +614,17 @@ class LUIManagerEditorMixin:
def draw_editor(self): def draw_editor(self):
"""Draw the LUI Editor Window""" """Draw the LUI Editor Window"""
if hasattr(self, "world") and hasattr(self.world, "showLUIEditor"):
self.show_editor = bool(self.world.showLUIEditor)
if not self.lui_enabled or not self.show_editor: if not self.lui_enabled or not self.show_editor:
return return
imgui.set_next_window_size((350, 600), imgui.Cond_.first_use_ever) imgui.set_next_window_size((350, 600), imgui.Cond_.first_use_ever)
with imgui_ctx.begin("LUI编辑器", True) as (opened, show): with imgui_ctx.begin("LUI编辑器", True) as (opened, show):
self.show_editor = opened self.show_editor = opened
if hasattr(self, "world") and hasattr(self.world, "showLUIEditor"):
self.world.showLUIEditor = opened
if not show: if not show:
return return

View File

@ -143,6 +143,8 @@ class EditorPanelsTopMixin:
_, self.app.showConsole = imgui.menu_item("控制台", "", self.app.showConsole, True) _, self.app.showConsole = imgui.menu_item("控制台", "", self.app.showConsole, True)
_, self.app.showScriptPanel = imgui.menu_item("脚本管理", "", self.app.showScriptPanel, True) _, self.app.showScriptPanel = imgui.menu_item("脚本管理", "", self.app.showScriptPanel, True)
_, self.app.showLUIEditor = imgui.menu_item("LUI编辑器", "", self.app.showLUIEditor, True) _, self.app.showLUIEditor = imgui.menu_item("LUI编辑器", "", self.app.showLUIEditor, True)
if hasattr(self.app, "lui_manager"):
self.app.lui_manager.show_editor = self.app.showLUIEditor
prev_show_web_panel = self.app.showWebPanel prev_show_web_panel = self.app.showWebPanel
_, self.app.showWebPanel = imgui.menu_item("Web面板", "", self.app.showWebPanel, True) _, self.app.showWebPanel = imgui.menu_item("Web面板", "", self.app.showWebPanel, True)
if prev_show_web_panel and not self.app.showWebPanel: if prev_show_web_panel and not self.app.showWebPanel: