fix: unify py311 startup and restore LUI editor panel toggle
This commit is contained in:
parent
718cf802be
commit
c5dbc6be6f
42
Start_Run.py
42
Start_Run.py
@ -1,9 +1,48 @@
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
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.11,LUI 可能不可用。")
|
||||
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 路径
|
||||
project_root = os.path.dirname(os.path.abspath(__file__))
|
||||
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)
|
||||
@ -22,6 +61,7 @@ sys.path.insert(0, icons_path)
|
||||
|
||||
# 现在可以导入并运行主程序
|
||||
if __name__ == "__main__":
|
||||
_maybe_relaunch_with_py311()
|
||||
args = sys.argv[1:]
|
||||
# args = "/home/tiger/桌面/Test1"
|
||||
# args = "C:/Users/29381/Desktop/1"
|
||||
@ -46,4 +86,4 @@ if __name__ == "__main__":
|
||||
else:
|
||||
# print(f"[DEBUG] 无项目路径,正常启动")
|
||||
app = MyWorld()
|
||||
app.run()
|
||||
app.run()
|
||||
|
||||
35
main.py
35
main.py
@ -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.11,LUI 可能不可用。")
|
||||
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 math import pi, sin, cos
|
||||
|
||||
@ -11,11 +41,6 @@ import p3dimgui
|
||||
|
||||
from imgui_bundle import imgui, imgui_ctx
|
||||
|
||||
import sys
|
||||
import os
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
|
||||
# 导入MyWorld类和必要的模块
|
||||
from core.world import CoreWorld
|
||||
from core.selection import SelectionSystem
|
||||
|
||||
33
third_party/p3dimgui/__init__.py
vendored
Normal file
33
third_party/p3dimgui/__init__.py
vendored
Normal 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
565
third_party/p3dimgui/backend.py
vendored
Normal 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
37
third_party/p3dimgui/shaders.py
vendored
Normal 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);
|
||||
}
|
||||
"""
|
||||
|
||||
26
third_party/p3dimgui/utilities/ExplorerManager.py
vendored
Normal file
26
third_party/p3dimgui/utilities/ExplorerManager.py
vendored
Normal 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]
|
||||
|
||||
88
third_party/p3dimgui/utilities/IntervalTimeSlider.py
vendored
Normal file
88
third_party/p3dimgui/utilities/IntervalTimeSlider.py
vendored
Normal 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))
|
||||
27
third_party/p3dimgui/utilities/PlaceManager.py
vendored
Normal file
27
third_party/p3dimgui/utilities/PlaceManager.py
vendored
Normal 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]
|
||||
|
||||
224
third_party/p3dimgui/utilities/PlacePanel.py
vendored
Normal file
224
third_party/p3dimgui/utilities/PlacePanel.py
vendored
Normal 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()
|
||||
|
||||
|
||||
171
third_party/p3dimgui/utilities/SceneGraphExplorer.py
vendored
Normal file
171
third_party/p3dimgui/utilities/SceneGraphExplorer.py
vendored
Normal 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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
26
third_party/p3dimgui/utilities/TimeSliderManager.py
vendored
Normal file
26
third_party/p3dimgui/utilities/TimeSliderManager.py
vendored
Normal 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]
|
||||
@ -614,12 +614,17 @@ class LUIManagerEditorMixin:
|
||||
|
||||
def draw_editor(self):
|
||||
"""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:
|
||||
return
|
||||
|
||||
imgui.set_next_window_size((350, 600), imgui.Cond_.first_use_ever)
|
||||
with imgui_ctx.begin("LUI编辑器", True) as (opened, show):
|
||||
self.show_editor = opened
|
||||
if hasattr(self, "world") and hasattr(self.world, "showLUIEditor"):
|
||||
self.world.showLUIEditor = opened
|
||||
if not show:
|
||||
return
|
||||
|
||||
|
||||
@ -143,6 +143,8 @@ class EditorPanelsTopMixin:
|
||||
_, self.app.showConsole = imgui.menu_item("控制台", "", self.app.showConsole, True)
|
||||
_, self.app.showScriptPanel = imgui.menu_item("脚本管理", "", self.app.showScriptPanel, 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
|
||||
_, self.app.showWebPanel = imgui.menu_item("Web面板", "", self.app.showWebPanel, True)
|
||||
if prev_show_web_panel and not self.app.showWebPanel:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user