MetaCoreEngineV2/main.py
2026-01-13 17:06:06 +08:00

295 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from panda3d.core import ConfigVariableString, NodePath,Filename,Material
from direct.showbase.ShowBase import ShowBase
import simplepbr
from rpcore.render_pipeline import RenderPipeline
import os,sys
import p3dimgui # Panda3D 的 ImGui 后端
from imgui_bundle import ImVec2, imgui # ImGui API
from tools.picker_ray import PickerRay
import panda3d.core as p3d
from pathlib import Path
sys.path.insert(0,os.path.dirname(__file__))
sys.path.insert(0, str(Path(__file__).resolve().parent / "Builtin"))
from TransformGizmo.transform_gizmo import TransformGizmo
from tools.camera_orbit_controller import CameraOrbitController
from classes.cornerBBox import CornerBBox
from classes.colors import Color
from rpcore.rpobject import RPObject
from classes.gameobject import GameObject
from classes.point_marker import PointMarker
import panda3d
import lui
panda3d.lui = lui
sys.modules["panda3d.lui"] = lui
from Builtin.LUIRegion import LUIRegion
from Builtin.LUIInputHandler import LUIInputHandler
from Builtin.LUIButton import LUIButton
from Builtin.LUISkin import LUIDefaultSkin
class MainApp(ShowBase):
def __init__(self):
ConfigVariableString('win-size').set_value('1280 720')
# super().__init__()
RPObject._OUTPUT_LEVEL = 1
self.render_pipeline = RenderPipeline()
self.render_pipeline.create(self)
self.setFrameRateMeter(True)
# 关闭窗口时确保主循环退出
if self.win:
self.win.setCloseRequestEvent("app-exit")
self.accept("app-exit", self.on_exit)
# 初始化 ImGui挂在 pixel2d 上并注册渲染回调
p3dimgui.init(window=self.win, parent=self.pixel2d, style='dark')
self.accept("imgui-new-frame", self.draw_imgui)
self.font_en = None
self.font_cn = None
self.node_icon = None
self.node_icon_size = ImVec2(16, 16)
self._init_imgui_fonts()
self._load_node_icon()
self.controller = CameraOrbitController(self)
self.gizmo = TransformGizmo(self)
self.models_root = self.render.attach_new_node("models_root")
self.picker = PickerRay(self,self.models_root)
model_path = "assets/Fox.glb"
infile = Filename.from_os_specific(os.path.abspath(model_path))
if os.path.exists(infile):
model: NodePath = self.loader.load_model(infile,noCache = True)
model.reparent_to(self.models_root)
GameObject.set_model_auto_scale(model)
mat = Material('unlit')
model.set_material(mat)
self.controller.set_target(model)
else:
print(f"模型路径不存在 -> {model_path}")
self.accept("mouse1", self.picker_evt)
self.corner_box:CornerBBox = None
panel:NodePath = GameObject.create_panel()
panel.wrtReparentTo(self.models_root)
self.marker = PointMarker(default_disable=True)
self._init_lui_button()
# region_width = 1.0
# region_height = (self.win.getYSize() - 48) / self.win.getYSize()
# dr = self.camNode.getDisplayRegion(0)
# dr.setDimensions(0, region_width, 0, region_height)
# # 鼠标坐标系跟随显示区域,否则拾取会偏移
# self.mouseWatcherNode.setDisplayRegion(dr)
# self._apply_region_aspect(dr)
# # 窗口变化时同步刷新镜头纵横比,避免再次挤压/拉伸
# self.accept("window-event", lambda win: self._apply_region_aspect(dr))
self.selected_node = None
self.hierarchy_size = ImVec2(300,0)
self.last_mouse_cursor = imgui.get_mouse_cursor()
# props = p3d.WindowProperties()
# props.setCursorHidden(True)
# self.win.requestProperties(props)
# imgui.get_io().mouse_draw_cursor = True
def _init_imgui_fonts(self):
# 为 ImGui 加载中英文字体,避免中文显示为方块
io = imgui.get_io()
# 英文字体:优先项目内提供,其次默认
font_en_path = Path("fonts/monaco.ttf")
if font_en_path.exists():
self.font_en = io.fonts.add_font_from_file_ttf(str(font_en_path), 18)
else:
self.font_en = io.fonts.add_font_default()
print(f'找不到英文字体文件 -> {font_en_path},已使用默认字体')
# 中文字体:尝试多个常见路径,选第一个可用的
candidate_cn_fonts = [
Path("fonts/msyh.ttc"),
Path("fonts/SourceHanSansCN-Regular.otf"),
Path("fonts/SourceHanSansSC-Regular.otf"),
Path("fonts/simhei.ttf"),
Path("fonts/simsun.ttc"),
Path("C:/Windows/Fonts/msyh.ttc"),
Path("C:/Windows/Fonts/SimHei.ttf"),
Path("C:/Windows/Fonts/simsun.ttc"),
]
cn_path = next((p for p in candidate_cn_fonts if p.exists()), None)
if cn_path:
cfg = imgui.ImFontConfig()
cfg.oversample_h = 2
cfg.oversample_v = 2
self.font_cn = io.fonts.add_font_from_file_ttf(str(cn_path), 18, cfg)
else:
self.font_cn = None
print("未找到可用的中文字体(尝试了项目 fonts/ 下和系统字体),中文可能显示为方框,请放入中文字体到 fonts/ 并重启程序。")
def _init_lui_button(self):
"""在右上角添加一个 LUI 按钮,演示 LUI 与现有管线共存。"""
# 创建 LUI 区域和输入处理
self.lui_region = LUIRegion.make("LUI", self.win)
self.lui_handler = LUIInputHandler()
self.mouseWatcher.attach_new_node(self.lui_handler)
self.lui_region.set_input_handler(self.lui_handler)
# 提高显示区域排序,确保覆盖在 RenderPipeline 的最终合成之上
self.lui_region.set_sort(8)
# 加载默认皮肤,保证按钮资源可用
self.lui_skin = LUIDefaultSkin()
self.lui_skin.load()
# 在右上角放置按钮
self.lui_button = LUIButton(parent=self.lui_region.root, text="LUI按钮", template="ButtonGreen")
self.lui_button.top = 20
self.lui_button.right = 20
self.lui_button.bind("click", lambda event: print("按钮被点击了", event))
def _load_node_icon(self):
icon_path = Path("assets/icons/model-color.png")
if not icon_path.exists():
print(f"找不到层级树图标 -> {icon_path}")
return
try:
self.node_icon = base.imgui.loadTexture(str(icon_path))
except Exception as exc:
self.node_icon = None
print(f"加载层级树图标失败 -> {icon_path}: {exc}")
def _apply_region_aspect(self, dr:p3d.DisplayRegion):
"""根据显示区域的实际宽高比调整镜头,保持 3D 画面比例不变。"""
if not self.win or not dr:
return
win_w = self.win.getXSize()
win_h = self.win.getYSize()
if win_h == 0:
return
region_w = dr.getRight() - dr.getLeft()
region_h = dr.getTop() - dr.getBottom()
if region_h == 0:
return
region_aspect = (win_w / win_h) * (region_w / region_h)
self.camLens.setAspectRatio(region_aspect)
def picker_evt(self):
# 如果 ImGui 捕获鼠标,则不执行 3D 拾取,避免 UI 上点击触发场景操作
try:
if base.imgui.isMouseCaptured():
# print("ImGui 捕获鼠标,不执行 3D 拾取")
return
except AttributeError:
pass
if self.gizmo.is_hovering:
return
if self.corner_box:
self.corner_box.remove()
node,point = self.picker.pick_object()
self.controller.set_target(node)
if node:
self.corner_box = CornerBBox(node)
self.gizmo.attach(node)
else:
self.gizmo.detach()
if point:
self.marker.update_point(point)
else:
self.marker.hide()
def draw_imgui(self):
io = imgui.get_io()
display_w, display_h = io.display_size
imgui.set_next_window_pos((0, 0), imgui.Cond_.always)
imgui.set_next_window_size((min(500,self.hierarchy_size.x), display_h), imgui.Cond_.always)
imgui.set_next_window_bg_alpha(1.0)
imgui.begin("Hierarchy", flags=imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_move)
imgui.push_style_var(imgui.StyleVar_.item_spacing, (4, 2)) # item之间间距
imgui.push_style_var(imgui.StyleVar_.frame_padding, (0, 0)) # item内部padding
imgui.push_style_var(imgui.StyleVar_.indent_spacing, 10) # 缩进更紧
imgui.push_font(self.font_cn,16)
self.draw_tree_node(self.render)
imgui.pop_font()
imgui.pop_style_var(3)
self.hierarchy_size = imgui.get_window_size()
# if imgui.get_mouse_cursor() != self.last_mouse_cursor:
# self.last_mouse_cursor = imgui.get_mouse_cursor()
# print(f"设置鼠标光标 -> {self.last_mouse_cursor}")
imgui.end()
def draw_tree_node(self, node: NodePath):
children = node.getChildren()
has_children = len(children) > 0
# --- 图标 ---
if self.node_icon:
imgui.image(self.node_icon, self.node_icon_size, (1, 1), (0, 0))
imgui.same_line()
label = f'{node.getName()} [{GameObject.get_node_type(node)}]'
# ✅ 叶子节点:不用 tree_node_ex完全无占位
if not has_children:
clicked, _ = imgui.selectable(
label,
p_selected=(node == self.selected_node),
flags=imgui.SelectableFlags_.span_all_columns |
imgui.SelectableFlags_.allow_double_click
)
if clicked:
self.selected_node = node
if imgui.is_mouse_double_clicked(imgui.MouseButton_.left):
# print(f"双击了节点 -> {node.getName()}")
pass
return
# --- 有子节点的才使用 tree node ---
flags = (
imgui.TreeNodeFlags_.open_on_arrow |
imgui.TreeNodeFlags_.span_avail_width |
imgui.TreeNodeFlags_.open_on_double_click
)
if node == self.selected_node:
flags |= imgui.TreeNodeFlags_.selected
if node.getName() == 'render':
imgui.set_next_item_open(True, imgui.Cond_.first_use_ever)
opened = imgui.tree_node_ex(label, flags)
if imgui.is_item_clicked(imgui.MouseButton_.left):
self.selected_node = node
if opened:
for child in children:
self.draw_tree_node(child)
imgui.tree_pop()
def on_exit(self):
"""关闭窗口时清理 ImGui 上下文并退出应用。"""
try:
self.imgui.cleanup()
except Exception:
pass
self.userExit()
MainApp().run()