diff --git a/demo.py b/demo.py index ce81e203..7daa4e03 100644 --- a/demo.py +++ b/demo.py @@ -248,6 +248,28 @@ class MyWorld(CoreWorld): self.showScriptPanel = True self.showToolbar = True self.showResourceManager = True + + # 变换监控相关 + self._transform_monitoring = False + self._monitored_node = None + self._last_transform_values = {} + self._transform_update_timer = 0 + self._transform_update_interval = 0.05 # 50ms检查一次 + self._clipboard_pos = None # 位置剪贴板 + + # 颜色选择器相关 + self._color_picker_active = False + self._color_picker_target = None # (target_object, property_name) + self._color_picker_current_color = (1.0, 1.0, 1.0, 1.0) + self._color_picker_callback = None + + # 字体选择器相关 + self._font_selector_active = False + self._font_selector_target = None # (target_object, property_name) + self._font_selector_current_font = "" + self._font_selector_callback = None + self._available_fonts = [] # 可用字体列表 + self._refresh_available_fonts() # 菜单状态管理 self.show_new_project_dialog = False @@ -579,6 +601,12 @@ class MyWorld(CoreWorld): self._draw_path_browser() self._draw_import_dialog() + # 绘制颜色选择器 + self._draw_color_picker() + + # 绘制字体选择器 + self._draw_font_selector() + # 绘制创建功能对话框 self._draw_3d_text_dialog() self._draw_3d_image_dialog() @@ -595,6 +623,9 @@ class MyWorld(CoreWorld): self._draw_terrain_dialog() self._draw_heightmap_browser() self._draw_script_dialog() + + # 绘制纹理选择对话框 + self._draw_texture_file_dialog() self._draw_script_browser() # 绘制右键菜单 @@ -603,6 +634,10 @@ class MyWorld(CoreWorld): # 绘制拖拽界面 self._draw_drag_drop_interface() + # 更新变换监控 + dt = imgui.get_io().delta_time + self.update_transform_monitoring(dt) + # 检查鼠标释放事件(用于处理拖拽结束) if imgui.is_mouse_released(0) and self.is_dragging: if not imgui.is_any_window_hovered(): @@ -1070,7 +1105,7 @@ class MyWorld(CoreWorld): # 使用TreeNode来显示目录 if is_selected: - imgui.push_style_color(imgui.Col_.header, imgui.IM_COL32(100, 150, 200, 255)) + imgui.push_style_color(imgui.Col_.header, (100/255, 150/255, 200/255, 1.0)) # 尝试加载PNG图标 icon_texture = None @@ -1262,7 +1297,7 @@ class MyWorld(CoreWorld): # 右键菜单 if rm.show_context_menu and rm.context_menu_file: - imgui.set_next_window_pos(imgui.ImVec2(rm.context_menu_position[0], rm.context_menu_position[1])) + imgui.set_next_window_pos((rm.context_menu_position[0], rm.context_menu_position[1])) with imgui_ctx.begin_popup("context_menu", imgui.WindowFlags_.no_title_bar | imgui.WindowFlags_.no_resize | imgui.WindowFlags_.always_auto_resize) as popup: if popup: @@ -1315,9 +1350,6 @@ class MyWorld(CoreWorld): with self.style_manager.begin_styled_window("属性面板", self.showPropertyPanel, flags): self.showPropertyPanel = True # 确保窗口保持打开 - imgui.text("对象属性") - imgui.separator() - # 获取当前选中的节点 selected_node = None if hasattr(self, 'selection') and self.selection and hasattr(self.selection, 'selectedNode'): @@ -1326,45 +1358,116 @@ class MyWorld(CoreWorld): if selected_node and not selected_node.isEmpty(): self._draw_node_properties(selected_node) else: - # 无选中对象时显示提示 - imgui.text_colored((0.5, 0.5, 0.5, 1.0), "未选择任何对象") - imgui.text("请从场景树中选择一个对象以查看其属性") + # 无选中对象时显示提示(模仿Qt版本的空状态样式) + imgui.spacing() + imgui.spacing() + + # 居中显示提示信息 + window_width = imgui.get_window_width() + text_width = 200 # 估算文本宽度 + text_pos_x = (window_width - text_width) / 2 + + imgui.set_cursor_pos_x(text_pos_x) + imgui.text_colored((0.5, 0.5, 0.5, 1.0), "🔍 未选择任何对象") + + imgui.set_cursor_pos_x(text_pos_x - 20) + imgui.text("请从场景树中选择一个对象") + + imgui.set_cursor_pos_x(text_pos_x + 10) + imgui.text("以查看其属性") + + imgui.spacing() + imgui.spacing() + + # 添加一些分隔线和装饰 + imgui.separator() + + # 显示快速提示 + imgui.text("💡 快速提示:") + imgui.bullet_text("单击场景树中的对象进行选择") + imgui.bullet_text("使用 F 键快速聚焦到选中对象") + imgui.bullet_text("使用 Delete 键删除选中对象") def _draw_node_properties(self, node): """绘制节点属性""" if not node or node.isEmpty(): + # 停止变换监控 + self.stop_transform_monitoring() return + # 检查是否需要重新启动变换监控 + if self._monitored_node != node: + self.stop_transform_monitoring() + self.start_transform_monitoring(node) + # 获取节点基本信息 node_name = node.getName() or "未命名节点" node_type = self._get_node_type_from_node(node) - # 节点名称和类型 - imgui.text(f"名称: {node_name}") - imgui.text(f"类型: {node_type}") + # 添加一些间距,模仿Qt版本的布局 + imgui.spacing() - # 状态徽章 - self._draw_status_badges(node) + # 物体名称组(使用Qt版本的样式) + if imgui.collapsing_header("物体名称", True): + # 第一行:可见性复选框和名称输入 + user_visible = node.getPythonTag("user_visible") + if user_visible is None: + user_visible = True + node.setPythonTag("user_visible", True) + + # 可见性复选框(模仿Qt版本的样式) + changed, is_visible = imgui.checkbox("##visibility", user_visible) + if changed: + node.setPythonTag("user_visible", is_visible) + if is_visible: + node.show() + else: + node.hide() + + imgui.same_line() + imgui.text("可见") + imgui.same_line() + imgui.spacing() + imgui.same_line() + + # 名称输入框(模仿Qt版本的样式) + imgui.text("名称:") + imgui.same_line() + changed, new_name = imgui.input_text("##name", node_name, 256) + if changed and hasattr(self, 'selection'): + # 更新场景树中的名称 + self._update_node_name(node, new_name) + + # 添加分隔线 + imgui.separator() + + # 状态徽章(模仿Qt版本的徽章样式) + self._draw_status_badges(node) - imgui.separator() + imgui.spacing() - # 变换属性 - self._draw_transform_properties(node) + # 变换属性组 + if imgui.collapsing_header("变换 Transform", True): + self._draw_transform_properties(node) - imgui.separator() - - # 根据节点类型显示特定属性 + # 根据节点类型显示特定属性组 if node_type == "GUI元素": - self._draw_gui_properties(node) + if imgui.collapsing_header("GUI信息"): + self._draw_gui_properties(node) elif node_type == "光源": - self._draw_light_properties(node) + if imgui.collapsing_header("光源属性"): + self._draw_light_properties(node) elif node_type == "模型": - self._draw_model_properties(node) + if imgui.collapsing_header("模型属性"): + self._draw_model_properties(node) - imgui.separator() + # 外观属性组(通用) + if imgui.collapsing_header("外观属性"): + self._draw_appearance_properties(node) - # 操作按钮 - self._draw_property_actions(node) + # 操作按钮组 + if imgui.collapsing_header("操作"): + self._draw_property_actions(node) def _get_node_type_from_node(self, node): """从节点判断其类型""" @@ -1390,92 +1493,432 @@ class MyWorld(CoreWorld): return "几何体" def _draw_status_badges(self, node): - """绘制状态徽章""" - # 可见性状态 + """绘制状态徽章(模仿Qt版本的徽章样式)""" + imgui.text("状态标签: ") + + # 可见性状态徽章 is_visible = not node.is_hidden() - visibility_color = (0.2, 0.8, 0.2, 1.0) if is_visible else (0.8, 0.2, 0.2, 1.0) + visibility_color = (0.176, 1.0, 0.769, 1.0) if is_visible else (0.953, 0.616, 0.471, 1.0) visibility_text = "可见" if is_visible else "隐藏" imgui.same_line() - imgui.text("状态: ") - imgui.same_line() - imgui.text_colored(visibility_color, f"● {visibility_text}") + imgui.text_colored(visibility_color, f"[{visibility_text}]") - # 是否有碰撞体 + # 节点类型徽章 + node_type = self._get_node_type_from_node(node) + type_colors = { + "GUI元素": (0.188, 0.404, 0.753, 1.0), # 主题蓝色 + "光源": (1.0, 0.8, 0.2, 1.0), # 黄色 + "模型": (0.6, 0.8, 1.0, 1.0), # 浅蓝色 + "相机": (0.8, 0.8, 0.2, 1.0), # 橙色 + "几何体": (0.5, 0.5, 0.5, 1.0), # 灰色 + } + + if node_type in type_colors: + imgui.same_line() + imgui.text_colored(type_colors[node_type], f"[{node_type}]") + + # 功能性徽章 + badges = [] + + # 碰撞体徽章 has_collision = hasattr(node, 'getChild') and any('Collision' in child.getName() for child in node.getChildren() if child.getName()) if has_collision: + badges.append(("碰撞", (0.2, 0.4, 0.8, 1.0))) # 蓝色 + + # 脚本徽章 + has_script = hasattr(node, 'getPythonTag') and node.getPythonTag('script') + if has_script: + badges.append(("脚本", (0.8, 0.4, 0.8, 1.0))) # 紫色 + + # 动画徽章 + has_animation = hasattr(node, 'getPythonTag') and node.getPythonTag('animation') + if has_animation: + badges.append(("动画", (0.4, 0.8, 0.4, 1.0))) # 绿色 + + # 材质徽章 + has_material = hasattr(node, 'getMaterial') and node.getMaterial() + if has_material: + badges.append(("材质", (0.8, 0.6, 0.2, 1.0))) # 金色 + + # 绘制功能性徽章 + for badge_text, badge_color in badges: imgui.same_line() - imgui.text_colored((0.2, 0.4, 0.8, 1.0), "● 碰撞") + imgui.text_colored(badge_color, f"[{badge_text}]") + + # 如果没有特殊徽章,显示默认状态 + if not badges: + imgui.same_line() + imgui.text_colored((0.5, 0.5, 0.5, 1.0), "[标准对象]") def _draw_transform_properties(self, node): """绘制变换属性""" - imgui.text("变换") + # 位置组 + if imgui.collapsing_header("位置 Position", True): + # 相对位置 + imgui.text("相对位置") + pos = node.getPos() + + # X坐标 + changed, new_x = imgui.input_float("X##pos_x", pos.x, 0.1, 1.0, "%.3f") + if changed: node.setX(new_x) + + # Y坐标 + changed, new_y = imgui.input_float("Y##pos_y", pos.y, 0.1, 1.0, "%.3f") + if changed: node.setY(new_y) + + # Z坐标 + changed, new_z = imgui.input_float("Z##pos_z", pos.z, 0.1, 1.0, "%.3f") + if changed: node.setZ(new_z) + + # 世界位置 + imgui.text("世界位置") + world_pos = node.getPos(self.render) + + imgui.text(f"世界 X: {world_pos.x:.3f}") + imgui.text(f"世界 Y: {world_pos.y:.3f}") + imgui.text(f"世界 Z: {world_pos.z:.3f}") + + # 位置操作按钮 + if imgui.button("重置位置##reset_pos"): + node.setPos(0, 0, 0) + imgui.same_line() + if imgui.button("复制位置##copy_pos"): + self._clipboard_pos = (pos.x, pos.y, pos.z) + imgui.same_line() + if imgui.button("粘贴位置##paste_pos") and hasattr(self, '_clipboard_pos'): + node.setPos(self._clipboard_pos[0], self._clipboard_pos[1], self._clipboard_pos[2]) - # 位置 - pos = node.getPos() - changed, new_x = imgui.drag_float("位置 X##pos", pos.x, 0.1) - if changed: node.setX(new_x) + # 旋转组 + if imgui.collapsing_header("旋转 Rotation"): + hpr = node.getHpr() + + # HPR旋转 + imgui.text("HPR 旋转 (度)") + changed, new_h = imgui.input_float("H##rot_h", hpr.x, 1.0, 10.0, "%.1f") + if changed: node.setH(new_h) + + changed, new_p = imgui.input_float("P##rot_p", hpr.y, 1.0, 10.0, "%.1f") + if changed: node.setP(new_p) + + changed, new_r = imgui.input_float("R##rot_r", hpr.z, 1.0, 10.0, "%.1f") + if changed: node.setR(new_r) + + # 旋转操作按钮 + if imgui.button("重置旋转##reset_rot"): + node.setHpr(0, 0, 0) + imgui.same_line() + if imgui.button("随机旋转##random_rot"): + import random + node.setHpr(random.randint(0, 360), random.randint(0, 360), random.randint(0, 360)) - changed, new_y = imgui.drag_float("位置 Y##pos", pos.y, 0.1) - if changed: node.setY(new_y) - - changed, new_z = imgui.drag_float("位置 Z##pos", pos.z, 0.1) - if changed: node.setZ(new_z) - - # 旋转 (度数) - hpr = node.getHpr() - changed, new_h = imgui.drag_float("旋转 H##rot", hpr.x, 1.0) - if changed: node.setH(new_h) - - changed, new_p = imgui.drag_float("旋转 P##rot", hpr.y, 1.0) - if changed: node.setP(new_p) - - changed, new_r = imgui.drag_float("旋转 R##rot", hpr.z, 1.0) - if changed: node.setR(new_r) - - # 缩放 - scale = node.getScale() - changed, new_sx = imgui.drag_float("缩放 X##scale", scale.x, 0.1) - if changed: node.setSx(new_sx) - - changed, new_sy = imgui.drag_float("缩放 Y##scale", scale.y, 0.1) - if changed: node.setSy(new_sy) - - changed, new_sz = imgui.drag_float("缩放 Z##scale", scale.z, 0.1) - if changed: node.setSz(new_sz) + # 缩放组 + if imgui.collapsing_header("缩放 Scale"): + scale = node.getScale() + + # XYZ缩放 + imgui.text("XYZ 缩放") + changed, new_sx = imgui.input_float("X##scale_x", scale.x, 0.1, 1.0, "%.3f") + if changed: node.setSx(new_sx) + + changed, new_sy = imgui.input_float("Y##scale_y", scale.y, 0.1, 1.0, "%.3f") + if changed: node.setSy(new_sy) + + changed, new_sz = imgui.input_float("Z##scale_z", scale.z, 0.1, 1.0, "%.3f") + if changed: node.setSz(new_sz) + + # 统一缩放 + if imgui.button("统一缩放##uniform_scale"): + uniform_scale = (scale.x + scale.y + scale.z) / 3.0 + node.setScale(uniform_scale, uniform_scale, uniform_scale) + imgui.same_line() + if imgui.button("重置缩放##reset_scale"): + node.setScale(1, 1, 1) + imgui.same_line() + if imgui.button("翻倍##double_scale"): + node.setScale(scale.x * 2, scale.y * 2, scale.z * 2) def _draw_gui_properties(self, node): """绘制GUI元素属性""" - imgui.text("GUI属性") - # 获取GUI元素 gui_element = None if hasattr(node, 'getPythonTag'): gui_element = node.getPythonTag('gui_element') - if gui_element: - # GUI类型 - gui_type = getattr(gui_element, 'gui_type', 'UNKNOWN') - imgui.text(f"GUI类型: {gui_type}") - + if not gui_element: + imgui.text("无GUI元素数据") + return + + # GUI类型信息 + gui_type = getattr(gui_element, 'gui_type', 'UNKNOWN') + imgui.text(f"GUI类型: {gui_type}") + + # 基本属性 + if imgui.collapsing_header("基本属性", True): # 文本内容 (适用于按钮、标签等) if hasattr(gui_element, 'text'): changed, new_text = imgui.input_text("文本内容", gui_element.text, 256) if changed and hasattr(self, 'gui_manager'): self.gui_manager.editGUIElement(gui_element, 'text', new_text) + # GUI ID + gui_id = getattr(gui_element, 'id', '') + changed, new_id = imgui.input_text("GUI ID", gui_id, 64) + if changed and hasattr(self, 'gui_manager'): + gui_element.id = new_id + + # 变换属性 + if imgui.collapsing_header("变换属性"): + # 位置 + pos = gui_element.getPos() + imgui.text("位置") + + if gui_type in ["button", "label", "entry", "2d_image"]: + # 2D GUI组件使用屏幕坐标 + imgui.text("屏幕坐标") + logical_x = pos.getX() / 0.1 + logical_z = pos.getZ() / 0.1 + + changed, new_x = imgui.input_float("X##gui_pos_x", logical_x, 1.0, 10.0, "%.1f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setX(new_x * 0.1) + + changed, new_z = imgui.input_float("Y##gui_pos_y", logical_z, 1.0, 10.0, "%.1f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setZ(new_z * 0.1) + else: + # 3D GUI组件使用世界坐标 + imgui.text("世界坐标") + changed, new_x = imgui.input_float("X##gui_world_x", pos.getX(), 0.1, 1.0, "%.3f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setX(new_x) + + changed, new_y = imgui.input_float("Y##gui_world_y", pos.getY(), 0.1, 1.0, "%.3f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setY(new_y) + + changed, new_z = imgui.input_float("Z##gui_world_z", pos.getZ(), 0.1, 1.0, "%.3f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setZ(new_z) + + # 缩放 + scale = gui_element.getScale() + imgui.text("缩放") + changed, new_sx = imgui.input_float("X##gui_scale_x", scale.getX(), 0.1, 1.0, "%.3f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setSx(new_sx) + + changed, new_sy = imgui.input_float("Y##gui_scale_y", scale.getY(), 0.1, 1.0, "%.3f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setSy(new_sy) + + changed, new_sz = imgui.input_float("Z##gui_scale_z", scale.getZ(), 0.1, 1.0, "%.3f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setSz(new_sz) + + # 旋转 + hpr = gui_element.getHpr() + imgui.text("旋转") + changed, new_h = imgui.input_float("H##gui_rot_h", hpr.getX(), 1.0, 10.0, "%.1f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setH(new_h) + + changed, new_p = imgui.input_float("P##gui_rot_p", hpr.getY(), 1.0, 10.0, "%.1f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setP(new_p) + + changed, new_r = imgui.input_float("R##gui_rot_r", hpr.getZ(), 1.0, 10.0, "%.1f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setR(new_r) + + # 外观属性 + if imgui.collapsing_header("外观属性"): # 大小 if hasattr(gui_element, 'size'): size = gui_element.size - changed, new_w = imgui.drag_float("宽度", size[0], 1.0) + imgui.text("大小") + changed, new_w = imgui.input_float("宽度", size[0], 1.0, 10.0, "%.1f") if changed and hasattr(self, 'gui_manager'): new_size = (new_w, size[1]) self.gui_manager.editGUIElement(gui_element, 'size', new_size) - changed, new_h = imgui.drag_float("高度", size[1], 1.0) + changed, new_h = imgui.input_float("高度", size[1], 1.0, 10.0, "%.1f") if changed and hasattr(self, 'gui_manager'): new_size = (size[0], new_h) self.gui_manager.editGUIElement(gui_element, 'size', new_size) + + # 颜色 + if hasattr(gui_element, 'getColor'): + try: + color = gui_element.getColor() + # 确保颜色是有效的 + if not color or (hasattr(color, '__len__') and len(color) < 3): + color = (1.0, 1.0, 1.0, 1.0) # 默认白色 + except: + color = (1.0, 1.0, 1.0, 1.0) # 默认白色 + + imgui.text("颜色") + # 获取颜色值 + if hasattr(color, 'getX'): + # 如果是Panda3D的Vec4对象 + r, g, b = color.getX(), color.getY(), color.getZ() + else: + # 如果是元组或其他格式 + r, g, b = color[0], color[1], color[2] + + changed, new_r = imgui.slider_float("R##gui_color_r", r, 0.0, 1.0) + if changed: gui_element.setColor(new_r, g, b, 1.0) + + changed, new_g = imgui.slider_float("G##gui_color_g", g, 0.0, 1.0) + if changed: gui_element.setColor(r, new_g, b, 1.0) + + changed, new_b = imgui.slider_float("B##gui_color_b", b, 0.0, 1.0) + if changed: gui_element.setColor(r, g, new_b, 1.0) + # 透明度 + imgui.text("透明度") + current_alpha = getattr(gui_element, 'alpha', 1.0) + changed, new_alpha = imgui.slider_float("Alpha", current_alpha, 0.0, 1.0) + if changed: + gui_element.alpha = new_alpha + if hasattr(gui_element, 'setTransparency'): + # 将0.0-1.0范围转换为Panda3D的透明度格式 + panda_transparency = int((1.0 - new_alpha) * 255) + gui_element.setTransparency(panda_transparency) + + # 渲染顺序 + imgui.text("渲染顺序") + current_sort = getattr(gui_element, 'sort', 0) + changed, new_sort = imgui.input_int("Sort Order", current_sort) + if changed: + gui_element.sort = new_sort + if hasattr(gui_element, 'setBin'): + gui_element.setBin('fixed', new_sort) + + # 字体设置(适用于文本类型的GUI元素) + if gui_type in ["button", "label", "entry"]: + imgui.text("字体设置") + + # 字体选择 + current_font = getattr(gui_element, 'font_path', '') + if imgui.button(f"字体: {Path(current_font).name if current_font else '默认'}##font_select"): + self.show_font_selector( + gui_element, + 'font_path', + current_font, + lambda font_path: self._apply_gui_font(gui_element, font_path) + ) + + # 字体大小 + current_size = getattr(gui_element, 'font_size', 12) + changed, new_size = imgui.slider_float("字体大小", current_size, 8.0, 72.0) + if changed: + gui_element.font_size = new_size + self._apply_gui_font_size(gui_element, new_size) + + # 字体样式 + imgui.text("字体样式") + is_bold = getattr(gui_element, 'font_bold', False) + changed, new_bold = imgui.checkbox("粗体", is_bold) + if changed: + gui_element.font_bold = new_bold + self._apply_gui_font_style(gui_element) + + imgui.same_line() + is_italic = getattr(gui_element, 'font_italic', False) + changed, new_italic = imgui.checkbox("斜体", is_italic) + if changed: + gui_element.font_italic = new_italic + self._apply_gui_font_style(gui_element) + + def _apply_gui_font(self, gui_element, font_path): + """应用GUI元素的字体""" + try: + if hasattr(gui_element, 'setFont') and font_path: + gui_element.setFont(font_path) + gui_element.font_path = font_path + except Exception as e: + print(f"应用GUI字体失败: {e}") + + def _apply_gui_font_size(self, gui_element, font_size): + """应用GUI元素的字体大小""" + try: + if hasattr(gui_element, 'setFontSize'): + gui_element.setFontSize(font_size) + gui_element.font_size = font_size + except Exception as e: + print(f"应用GUI字体大小失败: {e}") + + def _apply_gui_font_style(self, gui_element): + """应用GUI元素的字体样式""" + try: + if hasattr(gui_element, 'setFontStyle'): + style = 0 + if getattr(gui_element, 'font_bold', False): + style |= 1 # 粗体 + if getattr(gui_element, 'font_italic', False): + style |= 2 # 斜体 + gui_element.setFontStyle(style) + except Exception as e: + print(f"应用GUI字体样式失败: {e}") + + # 特定类型的属性 + if gui_type == "button": + if imgui.collapsing_header("按钮属性"): + # 按钮状态 + is_pressed = getattr(gui_element, 'pressed', False) + changed, new_pressed = imgui.checkbox("按下状态", is_pressed) + if changed: + gui_element.pressed = new_pressed + + # 按钮回调 + callback_name = getattr(gui_element, 'callback_name', '') + changed, new_callback = imgui.input_text("回调函数", callback_name, 64) + if changed: + gui_element.callback_name = new_callback + + elif gui_type == "entry": + if imgui.collapsing_header("输入框属性"): + # 输入框内容 + entry_text = getattr(gui_element, 'entry_text', '') + changed, new_text = imgui.input_text("输入内容", entry_text, 256) + if changed: + gui_element.entry_text = new_text + if hasattr(gui_element, 'set'): + gui_element.set(new_text) + + # 最大长度 + max_length = getattr(gui_element, 'max_length', 256) + changed, new_max = imgui.input_int("最大长度", max_length) + if changed: + gui_element.max_length = max(max_length, 1) + + # 密码模式 + is_password = getattr(gui_element, 'is_password', False) + changed, new_password = imgui.checkbox("密码模式", is_password) + if changed: + gui_element.is_password = new_password + if hasattr(gui_element, 'obscure'): + gui_element.obscure(new_password) + + elif gui_type in ["2d_image", "3d_image"]: + if imgui.collapsing_header("图像属性"): + # 图像路径 + image_path = getattr(gui_element, 'image_path', '') + changed, new_path = imgui.input_text("图像路径", image_path, 256) + if changed and hasattr(self, 'gui_manager'): + gui_element.image_path = new_path + # TODO: 重新加载图像 + + # 图像缩放模式 + scale_mode = getattr(gui_element, 'scale_mode', 'stretch') + if imgui.begin_combo("缩放模式", scale_mode): + if imgui.selectable("拉伸##stretch"): + gui_element.scale_mode = 'stretch' + if imgui.selectable("适应##fit"): + gui_element.scale_mode = 'fit' + if imgui.selectable("填充##fill"): + gui_element.scale_mode = 'fill' + imgui.end_combo() def _draw_light_properties(self, node): """绘制光源属性""" @@ -1483,15 +1926,22 @@ class MyWorld(CoreWorld): # 光源颜色 if hasattr(node, 'getColor'): - color = node.getColor() - changed, new_r = imgui.drag_float("颜色 R", color.x, 0.01, 0.0, 1.0) - if changed: node.setColor(new_r, color.y, color.z, color.w) + try: + color = node.getColor() + # 确保颜色是有效的 + if not color or len(color) < 3: + color = (1.0, 1.0, 1.0, 1.0) # 默认白色 + except: + color = (1.0, 1.0, 1.0, 1.0) # 默认白色 - changed, new_g = imgui.drag_float("颜色 G", color.y, 0.01, 0.0, 1.0) - if changed: node.setColor(color.x, new_g, color.z, color.w) + changed, new_r = imgui.drag_float("颜色 R", color[0], 0.01, 0.0, 1.0) + if changed: node.setColor(new_r, color[1], color[2], color[3] if len(color) > 3 else 1.0) - changed, new_b = imgui.drag_float("颜色 B", color.z, 0.01, 0.0, 1.0) - if changed: node.setColor(color.x, color.y, new_b, color.w) + changed, new_g = imgui.drag_float("颜色 G", color[1], 0.01, 0.0, 1.0) + if changed: node.setColor(color[0], new_g, color[2], color[3] if len(color) > 3 else 1.0) + + changed, new_b = imgui.drag_float("颜色 B", color[2], 0.01, 0.0, 1.0) + if changed: node.setColor(color[0], color[1], new_b, color[3] if len(color) > 3 else 1.0) # 光源强度 imgui.text("光源强度: (暂不支持编辑)") @@ -1531,6 +1981,1075 @@ class MyWorld(CoreWorld): if imgui.button("聚焦"): if hasattr(self, 'selection') and self.selection: self.selection.focusCameraOnSelectedNodeAdvanced() + + # 删除对象 + imgui.same_line() + if imgui.button("删除"): + if hasattr(self, 'selection') and self.selection: + self.selection.deleteSelectedNode() + + def _update_node_name(self, node, new_name): + """更新节点名称""" + if new_name and new_name != node.getName(): + node.setName(new_name) + # 更新场景树显示 + if hasattr(self, 'scene_tree'): + self.scene_tree.refresh() + + def _draw_appearance_properties(self, node): + """绘制外观属性""" + # 颜色属性 + if hasattr(node, 'getColor'): + imgui.text("颜色") + try: + color = node.getColor() + # 确保颜色是有效的 + if not color or len(color) < 3: + color = (1.0, 1.0, 1.0, 1.0) # 默认白色 + except: + color = (1.0, 1.0, 1.0, 1.0) # 默认白色 + + # 颜色滑块 + changed, new_r = imgui.slider_float("R##color_r", color[0], 0.0, 1.0) + if changed: + new_color = (new_r, color[1], color[2], color[3] if len(color) > 3 else 1.0) + node.setColor(new_color) + color = new_color + + changed, new_g = imgui.slider_float("G##color_g", color[1], 0.0, 1.0) + if changed: + new_color = (color[0], new_g, color[2], color[3] if len(color) > 3 else 1.0) + node.setColor(new_color) + color = new_color + + changed, new_b = imgui.slider_float("B##color_b", color[2], 0.0, 1.0) + if changed: + new_color = (color[0], color[1], new_b, color[3] if len(color) > 3 else 1.0) + node.setColor(new_color) + color = new_color + + # 只有当颜色有alpha通道时才显示alpha滑块 + if len(color) > 3: + changed, new_a = imgui.slider_float("A##color_a", color[3], 0.0, 1.0) + if changed: + new_color = (color[0], color[1], color[2], new_a) + node.setColor(new_color) + color = new_color + + # 颜色预览和选择器 + imgui.text("颜色预览") + color_with_alpha = (color[0], color[1], color[2], color[3] if len(color) > 3 else 1.0) + if imgui.color_button("颜色预览##preview", color_with_alpha, 0, (100, 30)): + # 点击颜色按钮打开颜色选择器 + self.show_color_picker(node, 'color', color_with_alpha) + + imgui.same_line() + if imgui.button("选择颜色##color_picker_btn"): + self.show_color_picker(node, 'color', (color.x, color.y, color.z, color.w)) + + # 透明度 + if hasattr(node, 'setTransparency') and hasattr(node, 'getTransparency'): + imgui.text("透明度") + current_transparency = node.getTransparency() + # 将当前的透明度值转换为0.0-1.0范围用于显示 + display_transparency = 1.0 - current_transparency if current_transparency <= 1 else 0.0 + changed, new_transparency = imgui.slider_float("透明度", display_transparency, 0.0, 1.0) + if changed: + # 将0.0-1.0范围转换回Panda3D的透明度格式 + panda_transparency = int((1.0 - new_transparency) * 255) + node.setTransparency(panda_transparency) + + # 材质属性 + self._draw_material_properties(node) + + # 渲染状态 + imgui.text("渲染状态") + if imgui.button("应用材质"): + self._apply_material_to_node(node) + + imgui.same_line() + if imgui.button("重置材质"): + self._reset_material(node) + + def _draw_material_properties(self, node): + """绘制材质属性""" + materials = node.find_all_materials() + + if not materials: + imgui.text_colored((0.5, 0.5, 0.5, 1.0), "无材质") + return + + for i, material in enumerate(materials): + material_name = material.get_name() if hasattr(material, 'get_name') and material.get_name() else f"材质{i + 1}" + + if imgui.collapsing_header(f"材质: {material_name}", True): + # 材质基础颜色 + base_color = self._get_material_base_color(material) + if base_color: + imgui.text("基础颜色") + changed, new_r = imgui.slider_float(f"R##mat_r_{i}", base_color[0], 0.0, 1.0) + if changed: + self._update_material_base_color(material, 'r', new_r) + base_color = (new_r, base_color[1], base_color[2], base_color[3]) + + changed, new_g = imgui.slider_float(f"G##mat_g_{i}", base_color[1], 0.0, 1.0) + if changed: + self._update_material_base_color(material, 'g', new_g) + base_color = (base_color[0], new_g, base_color[2], base_color[3]) + + changed, new_b = imgui.slider_float(f"B##mat_b_{i}", base_color[2], 0.0, 1.0) + if changed: + self._update_material_base_color(material, 'b', new_b) + base_color = (base_color[0], base_color[1], new_b, base_color[3]) + + changed, new_a = imgui.slider_float(f"A##mat_a_{i}", base_color[3], 0.0, 1.0) + if changed: + self._update_material_base_color(material, 'a', new_a) + base_color = (base_color[0], base_color[1], base_color[2], new_a) + + # PBR属性 + if hasattr(material, 'roughness') and material.roughness is not None: + imgui.text("PBR属性") + try: + roughness_value = float(material.roughness) + changed, new_roughness = imgui.slider_float(f"粗糙度##rough_{i}", roughness_value, 0.0, 1.0) + if changed: + self._update_material_roughness(material, new_roughness) + except: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "粗糙度: 不可用") + + if hasattr(material, 'metallic') and material.metallic is not None: + try: + metallic_value = float(material.metallic) + changed, new_metallic = imgui.slider_float(f"金属性##metal_{i}", metallic_value, 0.0, 1.0) + if changed: + self._update_material_metallic(material, new_metallic) + except: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "金属性: 不可用") + + if hasattr(material, 'refractive_index') and material.refractive_index is not None: + try: + ior_value = float(material.refractive_index) + changed, new_ior = imgui.slider_float(f"折射率##ior_{i}", ior_value, 1.0, 3.0) + if changed: + self._update_material_ior(material, new_ior) + except: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "折射率: 不可用") + + # 材质预设 + imgui.text("材质预设") + presets = ["默认", "金属", "塑料", "玻璃", "木材", "混凝土"] + current_preset = 0 # 默认选择 + + if imgui.begin_combo(f"预设##preset_{i}", presets[current_preset]): + for j, preset_name in enumerate(presets): + if imgui.selectable(preset_name, j == current_preset): + self._apply_material_preset(material, preset_name) + imgui.end_combo() + + # 纹理信息 + imgui.text("纹理贴图") + if imgui.button(f"选择漫反射贴图##diffuse_{i}"): + self._select_texture_for_material(node, material, "diffuse") + + imgui.same_line() + if imgui.button(f"选择法线贴图##normal_{i}"): + self._select_texture_for_material(node, material, "normal") + + imgui.same_line() + if imgui.button(f"选择粗糙度贴图##roughness_{i}"): + self._select_texture_for_material(node, material, "roughness") + + if imgui.button(f"选择金属性贴图##metallic_{i}"): + self._select_texture_for_material(node, material, "metallic") + + imgui.same_line() + if imgui.button(f"选择自发光贴图##emission_{i}"): + self._select_texture_for_material(node, material, "emission") + + imgui.same_line() + if imgui.button(f"清除所有贴图##clear_{i}"): + self._clear_all_textures(node) + + # 着色模型选择 + self._draw_shading_model_panel(material, i) + + # 显示当前纹理信息 + self._display_current_textures(node, material) + + def _get_material_base_color(self, material): + """获取材质基础颜色""" + try: + if hasattr(material, 'base_color') and material.base_color is not None: + return (material.base_color.x, material.base_color.y, material.base_color.z, material.base_color.w) + elif hasattr(material, 'get_base_color'): + color = material.get_base_color() + return (color.x, color.y, color.z, color.w) + elif hasattr(material, 'getDiffuse'): + color = material.getDiffuse() + return (color.x, color.y, color.z, color.w if hasattr(color, 'w') else 1.0) + else: + return (1.0, 1.0, 1.0, 1.0) # 默认白色 + except: + return (1.0, 1.0, 1.0, 1.0) # 默认白色 + + def _update_material_base_color(self, material, component, value): + """更新材质基础颜色""" + try: + base_color = self._get_material_base_color(material) + new_color = list(base_color) + + if component == 'r': + new_color[0] = value + elif component == 'g': + new_color[1] = value + elif component == 'b': + new_color[2] = value + elif component == 'a': + new_color[3] = value + + new_color_tuple = tuple(new_color) + + if hasattr(material, 'set_base_color'): + from panda3d.core import Vec4 + material.set_base_color(Vec4(*new_color_tuple)) + elif hasattr(material, 'setDiffuse'): + from panda3d.core import Vec4 + material.setDiffuse(Vec4(*new_color_tuple)) + except Exception as e: + print(f"更新材质基础颜色失败: {e}") + + def _update_material_roughness(self, material, value): + """更新材质粗糙度""" + try: + if hasattr(material, 'set_roughness'): + material.set_roughness(value) + except Exception as e: + print(f"更新材质粗糙度失败: {e}") + + def _update_material_metallic(self, material, value): + """更新材质金属性""" + try: + if hasattr(material, 'set_metallic'): + material.set_metallic(value) + except Exception as e: + print(f"更新材质金属性失败: {e}") + + def _update_material_ior(self, material, value): + """更新材质折射率""" + try: + if hasattr(material, 'set_refractive_index'): + material.set_refractive_index(value) + except Exception as e: + print(f"更新材质折射率失败: {e}") + + def _apply_material_preset(self, material, preset_name): + """应用材质预设""" + try: + from panda3d.core import Vec4, Material + + presets = { + "默认": { + "base_color": Vec4(0.8, 0.8, 0.8, 1.0), + "roughness": 0.5, + "metallic": 0.0, + "ior": 1.5 + }, + "金属": { + "base_color": Vec4(0.7, 0.7, 0.8, 1.0), + "roughness": 0.2, + "metallic": 1.0, + "ior": 2.5 + }, + "塑料": { + "base_color": Vec4(0.9, 0.9, 0.9, 1.0), + "roughness": 0.8, + "metallic": 0.0, + "ior": 1.45 + }, + "玻璃": { + "base_color": Vec4(0.9, 0.9, 1.0, 0.2), + "roughness": 0.0, + "metallic": 0.0, + "ior": 1.5 + }, + "木材": { + "base_color": Vec4(0.6, 0.4, 0.2, 1.0), + "roughness": 0.9, + "metallic": 0.0, + "ior": 1.55 + }, + "混凝土": { + "base_color": Vec4(0.5, 0.5, 0.5, 1.0), + "roughness": 1.0, + "metallic": 0.0, + "ior": 1.5 + } + } + + if preset_name in presets: + preset = presets[preset_name] + + # 应用基础颜色 + if hasattr(material, 'set_base_color'): + material.set_base_color(preset["base_color"]) + elif hasattr(material, 'setDiffuse'): + material.setDiffuse(preset["base_color"]) + + # 应用PBR属性 + if hasattr(material, 'set_roughness'): + material.set_roughness(preset["roughness"]) + if hasattr(material, 'set_metallic'): + material.set_metallic(preset["metallic"]) + if hasattr(material, 'set_refractive_index'): + material.set_refractive_index(preset["ior"]) + + print(f"已应用材质预设: {preset_name}") + except Exception as e: + print(f"应用材质预设失败: {e}") + + def _apply_material_to_node(self, node): + """为节点应用材质""" + try: + from panda3d.core import Material, Vec4 + + # 检查是否已有材质 + materials = node.find_all_materials() + + if not materials: + # 创建新材质 + material = Material(f"default-material-{node.getName()}") + material.setBaseColor(Vec4(0.8, 0.8, 0.8, 1.0)) + material.setDiffuse(Vec4(0.8, 0.8, 0.8, 1.0)) + material.setAmbient(Vec4(0.4, 0.4, 0.4, 1.0)) + material.setSpecular(Vec4(0.1, 0.1, 0.1, 1.0)) + material.setShininess(10.0) + node.setMaterial(material, 1) + print(f"已为新节点创建默认材质") + else: + print(f"节点已有 {len(materials)} 个材质") + except Exception as e: + print(f"应用材质失败: {e}") + + def _reset_material(self, node): + """重置节点材质""" + try: + materials = node.find_all_materials() + + for material in materials: + # 重置为默认材质属性 + from panda3d.core import Vec4 + default_color = Vec4(0.8, 0.8, 0.8, 1.0) + + if hasattr(material, 'set_base_color'): + material.set_base_color(default_color) + elif hasattr(material, 'setDiffuse'): + material.setDiffuse(default_color) + + if hasattr(material, 'set_roughness'): + material.set_roughness(0.5) + if hasattr(material, 'set_metallic'): + material.set_metallic(0.0) + if hasattr(material, 'set_refractive_index'): + material.set_refractive_index(1.5) + + print(f"已重置材质") + except Exception as e: + print(f"重置材质失败: {e}") + + def _select_texture_for_material(self, node, material, texture_type): + """为材质选择纹理""" + try: + # 设置当前纹理对话框状态 + self._current_texture_dialog = { + 'node': node, + 'material': material, + 'texture_type': texture_type + } + + # 初始化路径 + if not hasattr(self, '_texture_dialog_path'): + self._texture_dialog_path = '/home/hello/EG/Resources' + + # 设置文件类型过滤 + self._texture_dialog_filter = "*.png" + + except Exception as e: + print(f"选择纹理失败: {e}") + + def _apply_texture_to_material(self, node, material, texture_type, texture_path): + """应用纹理到材质""" + try: + from panda3d.core import TextureStage + from direct.showbase import Loader + + # 加载纹理 + loader = Loader.Loader(self) + texture = loader.loadTexture(texture_path) + + if not texture: + print(f"无法加载纹理: {texture_path}") + return + + # 设置纹理属性 + texture.setMagfilter(texture.FTLinear) + texture.setMinfilter(texture.FTLinearMipmapLinear) + + # 纹理槽位映射 + texture_slots = { + "diffuse": 0, # p3d_Texture0 + "ior": 2, # p3d_Texture2 + "normal": 1, # p3d_Texture1 + "roughness": 3, # p3d_Texture3 + "parallax": 4, # p3d_Texture4 + "metallic": 5, # p3d_Texture5 + "emission": 6, # p3d_Texture6 + "ao": 7, # p3d_Texture7 + "alpha": 8, # p3d_Texture8 + "detail": 9, # p3d_Texture9 + "gloss": 10 # p3d_Texture10 + } + + slot = texture_slots.get(texture_type, 0) + + # 创建纹理阶段 + texture_stage = TextureStage(f"{texture_type}_map") + texture_stage.setSort(slot) + texture_stage.setMode(TextureStage.MModulate) + + # 应用纹理到节点 + node.setTexture(texture_stage, texture) + + print(f"已应用{texture_type}纹理: {texture_path}") + + except Exception as e: + print(f"应用纹理失败: {e}") + + def _clear_all_textures(self, node): + """清除节点所有纹理""" + try: + # 清除所有纹理阶段 + node.clearTexture() + node.clearTexture() + print("已清除所有纹理") + except Exception as e: + print(f"清除纹理失败: {e}") + + def _display_current_textures(self, node, material): + """显示当前纹理信息""" + try: + from panda3d.core import TextureStage + + # 获取所有纹理阶段 + texture_stages = node.findAllTextureStages() + + if not texture_stages: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "当前无纹理") + return + + imgui.text("当前纹理:") + for stage in texture_stages: + texture = node.getTexture(stage) + if texture: + texture_name = texture.getName() or "未命名" + stage_name = stage.getName() or "未命名" + imgui.text(f" {stage_name}: {texture_name}") + except Exception as e: + print(f"显示纹理信息失败: {e}") + + def _draw_shading_model_panel(self, material, material_index): + """绘制着色模型选择面板""" + try: + imgui.text("着色模型") + + # RenderPipeline支持的着色模型 + shading_models = ["默认", "自发光", "透明"] + current_model = 0 # 默认选择 + + # 安全地获取当前着色模型 + try: + if hasattr(material, 'emission') and material.emission is not None: + current_model = int(material.emission.x) + except: + current_model = 0 + + # 着色模型选择 + if imgui.begin_combo(f"着色模型##shading_{material_index}", shading_models[current_model]): + for j, model_name in enumerate(shading_models): + if imgui.selectable(model_name, j == current_model): + self._update_shading_model(material, j) + imgui.end_combo() + + # 如果是透明着色模型,添加透明度控制 + if current_model == 3: # 透明着色模型 + imgui.text("透明度设置") + try: + if hasattr(material, 'shading_model_param0'): + current_opacity = material.shading_model_param0 + else: + current_opacity = 1.0 + + changed, new_opacity = imgui.slider_float(f"不透明度##opacity_{material_index}", current_opacity, 0.0, 1.0) + if changed: + self._update_transparency(material, new_opacity) + except: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "透明度控制不可用") + + except Exception as e: + print(f"绘制着色模型面板失败: {e}") + + def _update_shading_model(self, material, model_index): + """更新着色模型""" + try: + from panda3d.core import Vec4 + + # 根据不同的着色模型设置相应的参数 + if model_index == 1: # 自发光着色模型 + print("设置自发光着色模型...") + if hasattr(material, 'set_emission'): + current_emission = material.emission or Vec4(0, 0, 0, 0) + new_emission = Vec4(1.0, current_emission.y, current_emission.z, current_emission.w) + material.set_emission(new_emission) + print("自发光着色模型设置完成") + + elif model_index == 3: # 透明着色模型 + print("设置透明着色模型...") + if hasattr(material, 'set_emission'): + current_emission = material.emission or Vec4(0, 0, 0, 0) + new_emission = Vec4(3.0, 0, 0, 0) # 3表示透明着色模型 + material.set_emission(new_emission) + + # 设置默认透明度 + if hasattr(material, 'shading_model_param0'): + material.shading_model_param0 = 0.8 # 默认80%不透明度 + + print("透明着色模型设置完成") + + else: # 默认着色模型 + print("设置默认着色模型...") + if hasattr(material, 'set_emission'): + current_emission = material.emission or Vec4(0, 0, 0, 0) + new_emission = Vec4(0.0, current_emission.y, current_emission.z, current_emission.w) + material.set_emission(new_emission) + print("默认着色模型设置完成") + + print(f"着色模型已更新为: {model_index} ({'自发光' if model_index == 1 else '透明' if model_index == 3 else '默认'})") + + except Exception as e: + print(f"更新着色模型失败: {e}") + + def _update_transparency(self, material, opacity_value): + """更新透明度""" + try: + if hasattr(material, 'shading_model_param0'): + material.shading_model_param0 = opacity_value + print(f"透明度已更新: {opacity_value}") + except Exception as e: + print(f"更新透明度失败: {e}") + + def _draw_texture_file_dialog(self): + """绘制纹理文件选择对话框""" + if not hasattr(self, '_current_texture_dialog') or not self._current_texture_dialog: + return + + try: + dialog_data = self._current_texture_dialog + node = dialog_data['node'] + material = dialog_data['material'] + texture_type = dialog_data['texture_type'] + + # 设置对话框标志 + flags = (imgui.WindowFlags_.no_resize | + imgui.WindowFlags_.no_collapse | + imgui.WindowFlags_.modal) + + # 获取屏幕尺寸,居中显示对话框 + display_size = imgui.get_io().display_size + dialog_width = 600 + dialog_height = 400 + imgui.set_next_window_size((dialog_width, dialog_height)) + imgui.set_next_window_pos( + ((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2) + ) + + # 显示文件选择对话框 + opened, window_open = imgui.begin(f"选择{texture_type}纹理文件##texture_dialog", True, flags) + if not window_open: + self._current_texture_dialog = None + imgui.end() + return + + imgui.text(f"选择{texture_type}纹理文件") + imgui.separator() + + # 当前路径显示 + current_path = getattr(self, '_texture_dialog_path', '/home/hello/EG/Resources') + imgui.text(f"当前路径: {current_path}") + + imgui.separator() + + # 文件类型过滤 + imgui.text("支持的纹理格式:") + file_types = ["*.png", "*.jpg", "*.jpeg", "*.bmp", "*.tga", "*.dds"] + + current_filter = getattr(self, '_texture_dialog_filter', "*.png") + if imgui.begin_combo("文件类型##texture_filter", current_filter): + for i, file_type in enumerate(file_types): + if imgui.selectable(file_type, i == file_types.index(current_filter)): + self._texture_dialog_filter = file_type + imgui.end_combo() + + imgui.separator() + + # 路径导航按钮 + if imgui.button("上级目录##up_dir"): + current_path = os.path.dirname(current_path) + self._texture_dialog_path = current_path + + imgui.same_line() + if imgui.button("主目录##home_dir"): + self._texture_dialog_path = '/home/hello/EG/Resources' + + imgui.same_line() + if imgui.button("当前目录##current_dir"): + self._texture_dialog_path = '/home/hello/EG/Resources' + + imgui.same_line() + if imgui.button("纹理目录##textures_dir"): + self._texture_dialog_path = '/home/hello/EG/Resources/textures' + + imgui.separator() + + # 文件列表 + if imgui.begin_child("file_list##texture_files", (580, 200)): + try: + # 列出目录和文件 + items = [] + if os.path.exists(current_path): + for item in os.listdir(current_path): + item_path = os.path.join(current_path, item) + if os.path.isdir(item_path): + items.append(('dir', item, item_path)) + elif any(item.lower().endswith(ext[1:]) for ext in file_types): + items.append(('file', item, item_path)) + + # 排序:目录在前,文件在后 + items.sort(key=lambda x: (x[0], x[1].lower())) + + for item_type, item_name, item_path in items: + if item_type == 'dir': + if imgui.selectable(f"📁 {item_name}##dir_{item_name}", False)[0]: + self._texture_dialog_path = item_path + else: + selected, _ = imgui.selectable(f"📄 {item_name}##file_{item_name}", False) + if selected: + # 应用选择的纹理 + self._apply_texture_to_material(node, material, texture_type, item_path) + # 关闭对话框 + self._current_texture_dialog = None + break + + except Exception as e: + imgui.text_colored((1.0, 0.5, 0.5, 1.0), f"读取目录失败: {e}") + + imgui.end_child() + + imgui.separator() + + # 路径输入框 + changed, new_path = imgui.input_text("文件路径##texture_path", current_path, 512) + if changed: + self._texture_dialog_path = new_path + + imgui.same_line() + if imgui.button("确认路径##confirm_path"): + if os.path.isfile(new_path): + # 检查文件扩展名 + file_ext = os.path.splitext(new_path)[1].lower() + if file_ext in [ext[1:] for ext in file_types]: + self._apply_texture_to_material(node, material, texture_type, new_path) + self._current_texture_dialog = None + else: + print(f"不支持的文件格式: {file_ext}") + else: + print("请选择有效的文件") + + imgui.separator() + + # 按钮 + if imgui.button("取消##cancel_texture"): + self._current_texture_dialog = None + + imgui.end() + + except Exception as e: + print(f"绘制纹理对话框失败: {e}") + # 确保在异常情况下也调用 imgui.end() + try: + imgui.end() + except: + pass + + def start_transform_monitoring(self, node): + """开始变换监控""" + if node and not node.isEmpty(): + self._monitored_node = node + self._transform_monitoring = True + self._transform_update_timer = 0 + + # 记录初始变换值 + self._update_last_transform_values() + + def stop_transform_monitoring(self): + """停止变换监控""" + self._transform_monitoring = False + self._monitored_node = None + self._last_transform_values = {} + + def _update_last_transform_values(self): + """更新最后记录的变换值""" + if self._monitored_node and not self._monitored_node.isEmpty(): + try: + pos = self._monitored_node.getPos() + hpr = self._monitored_node.getHpr() + scale = self._monitored_node.getScale() + + self._last_transform_values = { + 'pos': (pos.x, pos.y, pos.z), + 'hpr': (hpr.x, hpr.y, hpr.z), + 'scale': (scale.x, scale.y, scale.z) + } + except Exception as e: + print(f"更新变换值失败: {e}") + + def _check_transform_changes(self): + """检查变换变化""" + if not self._transform_monitoring or not self._monitored_node: + return + + try: + pos = self._monitored_node.getPos() + hpr = self._monitored_node.getHpr() + scale = self._monitored_node.getScale() + + current_values = { + 'pos': (pos.x, pos.y, pos.z), + 'hpr': (hpr.x, hpr.y, hpr.z), + 'scale': (scale.x, scale.y, scale.z) + } + + # 检查是否有变化 + if current_values != self._last_transform_values: + # 更新记录的值 + self._last_transform_values = current_values + # 触发属性面板更新(通过设置更新标志) + self.property_panel_update_timer = 0 + + except Exception as e: + print(f"检查变换变化失败: {e}") + + def update_transform_monitoring(self, dt): + """更新变换监控(在主循环中调用)""" + if not self._transform_monitoring: + return + + self._transform_update_timer += dt + if self._transform_update_timer >= self._transform_update_interval: + self._transform_update_timer = 0 + self._check_transform_changes() + + def show_color_picker(self, target_object, property_name, initial_color, callback=None): + """显示颜色选择器""" + self._color_picker_active = True + self._color_picker_target = (target_object, property_name) + self._color_picker_current_color = initial_color + self._color_picker_callback = callback + + def _draw_color_picker(self): + """绘制颜色选择器对话框""" + if not self._color_picker_active: + return + + # 设置对话框标志 + flags = (imgui.WindowFlags_.no_resize | + imgui.WindowFlags_.no_collapse | + imgui.WindowFlags_.modal) + + # 获取屏幕尺寸,居中显示对话框 + display_size = imgui.get_io().display_size + dialog_width = 300 + dialog_height = 400 + imgui.set_next_window_size((dialog_width, dialog_height)) + imgui.set_next_window_pos( + ((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2) + ) + + with imgui_ctx.begin("颜色选择器", True, flags) as window: + if not window.opened: + self._color_picker_active = False + self._color_picker_target = None + return + + imgui.text("选择颜色") + imgui.separator() + + # 颜色编辑器 + changed, new_color = imgui.color_edit4( + "颜色##color_picker", + self._color_picker_current_color + ) + + if changed: + self._color_picker_current_color = new_color + + # 预设颜色 + imgui.text("预设颜色") + preset_colors = [ + (1.0, 0.0, 0.0, 1.0), # 红色 + (0.0, 1.0, 0.0, 1.0), # 绿色 + (0.0, 0.0, 1.0, 1.0), # 蓝色 + (1.0, 1.0, 0.0, 1.0), # 黄色 + (1.0, 0.0, 1.0, 1.0), # 洋红 + (0.0, 1.0, 1.0, 1.0), # 青色 + (1.0, 1.0, 1.0, 1.0), # 白色 + (0.0, 0.0, 0.0, 1.0), # 黑色 + (0.5, 0.5, 0.5, 1.0), # 灰色 + (0.188, 0.404, 0.753, 1.0), # 主题蓝色 + (0.176, 1.0, 0.769, 1.0), # 主题绿色 + (0.953, 0.616, 0.471, 1.0), # 主题橙色 + ] + + # 绘制预设颜色按钮 + colors_per_row = 6 + for i, color in enumerate(preset_colors): + if i % colors_per_row != 0: + imgui.same_line() + + imgui.color_button(f"preset_{i}", color, 0, (30, 30)) + if imgui.is_item_clicked(): + self._color_picker_current_color = color + + imgui.separator() + + # 按钮区域 + if imgui.button("确定"): + self._apply_color_selection() + self._color_picker_active = False + self._color_picker_target = None + + imgui.same_line() + if imgui.button("取消"): + self._color_picker_active = False + self._color_picker_target = None + + def _apply_color_selection(self): + """应用颜色选择""" + if not self._color_picker_target: + return + + target_object, property_name = self._color_picker_target + + try: + # 应用颜色到目标对象 + if hasattr(target_object, 'setColor'): + target_object.setColor(self._color_picker_current_color) + elif hasattr(target_object, property_name): + setattr(target_object, property_name, self._color_picker_current_color) + + # 调用回调函数 + if self._color_picker_callback: + self._color_picker_callback(self._color_picker_current_color) + + except Exception as e: + print(f"应用颜色失败: {e}") + + def _draw_color_button(self, label, color, size=(50, 20)): + """绘制颜色按钮并支持点击打开颜色选择器""" + imgui.color_button(label, color, 0, size) + if imgui.is_item_clicked(): + # 打开颜色选择器 + self.show_color_picker(None, None, color) + + def _refresh_available_fonts(self): + """刷新可用字体列表""" + try: + import platform + from pathlib import Path + + system = platform.system().lower() + font_paths = [] + + if system == "linux": + font_dirs = [ + "/usr/share/fonts/truetype/", + "/usr/share/fonts/opentype/", + "/usr/local/share/fonts/", + "~/.fonts/" + ] + elif system == "windows": + font_dirs = [ + "C:/Windows/Fonts/", + ] + elif system == "darwin": + font_dirs = [ + "/System/Library/Fonts/", + "/Library/Fonts/", + "~/Library/Fonts/" + ] + else: + font_dirs = [] + + # 扫描字体目录 + common_fonts = [] + for font_dir in font_dirs: + font_path = Path(font_dir).expanduser() + if font_path.exists(): + for font_file in font_path.rglob("*.ttf"): + common_fonts.append(str(font_file)) + for font_file in font_path.rglob("*.otf"): + common_fonts.append(str(font_file)) + for font_file in font_path.rglob("*.ttc"): + common_fonts.append(str(font_file)) + + # 过滤常见字体 + font_keywords = [ + "arial", "helvetica", "times", "courier", "verdana", "georgia", + "comic", "impact", "trebuchet", "palatino", "garamond", + "noto", "dejavu", "liberation", "ubuntu", "roboto", "open", + "droid", "source", "wenquanyi", "wqy", "pingfang", "stheiti", + "microsoft", "msyh", "simsun", "simhei", "kaiti", "fangsong" + ] + + self._available_fonts = [] + for font_path in common_fonts: + font_name = Path(font_path).name.lower() + if any(keyword in font_name for keyword in font_keywords): + self._available_fonts.append(font_path) + + # 添加一些默认字体路径 + default_fonts = [ + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", + "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", + "/usr/share/fonts/opentype/noto/NotoSans-Regular.ttf", + "C:/Windows/Fonts/arial.ttf", + "C:/Windows/Fonts/msyh.ttc", + "/System/Library/Fonts/PingFang.ttc" + ] + + for font_path in default_fonts: + if Path(font_path).exists() and font_path not in self._available_fonts: + self._available_fonts.append(font_path) + + print(f"✓ 找到 {len(self._available_fonts)} 个可用字体") + + except Exception as e: + print(f"⚠ 字体扫描失败: {e}") + self._available_fonts = [] + + def show_font_selector(self, target_object, property_name, current_font, callback=None): + """显示字体选择器""" + self._font_selector_active = True + self._font_selector_target = (target_object, property_name) + self._font_selector_current_font = current_font or "" + self._font_selector_callback = callback + + def _draw_font_selector(self): + """绘制字体选择器对话框""" + if not self._font_selector_active: + return + + # 设置对话框标志 + flags = (imgui.WindowFlags_.no_resize | + imgui.WindowFlags_.no_collapse | + imgui.WindowFlags_.modal) + + # 获取屏幕尺寸,居中显示对话框 + display_size = imgui.get_io().display_size + dialog_width = 400 + dialog_height = 500 + imgui.set_next_window_size((dialog_width, dialog_height)) + imgui.set_next_window_pos( + ((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2) + ) + + with imgui_ctx.begin("字体选择器", True, flags) as window: + if not window.opened: + self._font_selector_active = False + self._font_selector_target = None + return + + imgui.text("选择字体") + imgui.separator() + + # 当前字体显示 + imgui.text(f"当前字体: {self._font_selector_current_font or '默认'}") + + # 字体搜索框 + changed, search_text = imgui.input_text("搜索", "", 256) + imgui.separator() + + # 字体列表 + if imgui.begin_child("font_list", (380, 300)): + for font_path in self._available_fonts: + font_name = Path(font_path).name + + # 搜索过滤 + if search_text and search_text.lower() not in font_name.lower(): + continue + + # 字体项 + if imgui.selectable(font_name, font_path == self._font_selector_current_font): + self._font_selector_current_font = font_path + + # 显示字体路径作为工具提示 + if imgui.is_item_hovered(): + imgui.set_tooltip(font_path) + + imgui.end_child() + + imgui.separator() + + # 按钮区域 + if imgui.button("确定"): + self._apply_font_selection() + self._font_selector_active = False + self._font_selector_target = None + + imgui.same_line() + if imgui.button("取消"): + self._font_selector_active = False + self._font_selector_target = None + + imgui.same_line() + if imgui.button("刷新字体"): + self._refresh_available_fonts() + + def _apply_font_selection(self): + """应用字体选择""" + if not self._font_selector_target: + return + + target_object, property_name = self._font_selector_target + + try: + # 应用字体到目标对象 + if hasattr(target_object, property_name): + setattr(target_object, property_name, self._font_selector_current_font) + + # 调用回调函数 + if self._font_selector_callback: + self._font_selector_callback(self._font_selector_current_font) + + except Exception as e: + print(f"应用字体失败: {e}") + + def _draw_font_selector_button(self, label, current_font): + """绘制字体选择器按钮""" + font_name = Path(current_font).name if current_font else "默认字体" + display_text = f"{font_name[:20]}..." if len(font_name) > 20 else font_name + + if imgui.button(f"{label}: {display_text}##font_selector"): + self.show_font_selector(None, None, current_font) def _draw_console(self): """绘制控制台面板""" @@ -3664,7 +5183,14 @@ class MyWorld(CoreWorld): self.scene_manager.processMaterials(model_node) # 设置默认的基础颜色(如果模型没有颜色) - if model_node.getColor() == (1, 1, 1, 1): # 默认白色 + try: + color = model_node.getColor() + if color and len(color) >= 4 and color == (1, 1, 1, 1): # 默认白色 + model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰 + elif not color: # 如果没有颜色 + model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰 + except: + # 如果getColor失败,直接设置默认颜色 model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰 except Exception as e: diff --git a/imgui.ini b/imgui.ini index 3ef3fe6e..1c34eff5 100644 --- a/imgui.ini +++ b/imgui.ini @@ -25,7 +25,7 @@ Collapsed=0 [Window][工具栏] Pos=287,20 -Size=1237,32 +Size=1234,32 Collapsed=0 DockId=0x00000007,0 @@ -36,20 +36,20 @@ Collapsed=0 DockId=0x00000001,0 [Window][属性面板] -Pos=1526,20 -Size=324,498 +Pos=1523,20 +Size=327,498 Collapsed=0 DockId=0x00000005,0 [Window][控制台] Pos=880,798 -Size=644,218 +Size=641,218 Collapsed=0 DockId=0x0000000C,0 [Window][脚本管理] -Pos=1526,520 -Size=324,496 +Pos=1523,520 +Size=327,496 Collapsed=0 DockId=0x00000006,0 @@ -134,18 +134,28 @@ Pos=60,60 Size=89,250 Collapsed=0 +[Window][颜色选择器] +Pos=775,308 +Size=300,400 +Collapsed=0 + +[Window][选择diffuse纹理文件##texture_dialog] +Pos=625,308 +Size=600,400 +Collapsed=0 + [Docking][Data] DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1850,996 Split=X - DockNode ID=0x00000003 Parent=0x08BD597D SizeRef=1524,996 Split=Y + DockNode ID=0x00000003 Parent=0x08BD597D SizeRef=1521,996 Split=Y DockNode ID=0x00000009 Parent=0x00000003 SizeRef=1380,776 Split=X DockNode ID=0x00000001 Parent=0x00000009 SizeRef=285,730 HiddenTabBar=1 Selected=0xE0015051 - DockNode ID=0x00000002 Parent=0x00000009 SizeRef=1237,730 Split=Y + DockNode ID=0x00000002 Parent=0x00000009 SizeRef=1234,730 Split=Y DockNode ID=0x00000007 Parent=0x00000002 SizeRef=1380,32 HiddenTabBar=1 Selected=0x43A39006 DockNode ID=0x00000008 Parent=0x00000002 SizeRef=1380,742 CentralNode=1 Selected=0x5E5F7166 DockNode ID=0x0000000A Parent=0x00000003 SizeRef=1380,218 Split=X Selected=0x5428E753 DockNode ID=0x0000000B Parent=0x0000000A SizeRef=878,111 HiddenTabBar=1 Selected=0x3A2E05C3 - DockNode ID=0x0000000C Parent=0x0000000A SizeRef=644,111 HiddenTabBar=1 Selected=0x5428E753 - DockNode ID=0x00000004 Parent=0x08BD597D SizeRef=324,996 Split=Y Selected=0x5DB6FF37 + DockNode ID=0x0000000C Parent=0x0000000A SizeRef=641,111 HiddenTabBar=1 Selected=0x5428E753 + DockNode ID=0x00000004 Parent=0x08BD597D SizeRef=327,996 Split=Y Selected=0x5DB6FF37 DockNode ID=0x00000005 Parent=0x00000004 SizeRef=304,498 HiddenTabBar=1 Selected=0x5DB6FF37 DockNode ID=0x00000006 Parent=0x00000004 SizeRef=304,496 HiddenTabBar=1 Selected=0x3188AB8D diff --git a/scene/scene_manager.py b/scene/scene_manager.py index e59d0dbf..e3f49121 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -132,6 +132,13 @@ class SceneManager: if not model_name: model_name = "imported_model" model.setName(model_name) + + # 设置默认颜色以避免get_color警告 + try: + model.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰 + except: + pass # 如果设置颜色失败,继续执行 + # 将模型添加到场景 model.reparentTo(self.world.render)