diff --git a/QPanda3D/QPanda3DWidget.py b/QPanda3D/QPanda3DWidget.py index 25a6158c..249fc355 100644 --- a/QPanda3D/QPanda3DWidget.py +++ b/QPanda3D/QPanda3DWidget.py @@ -160,8 +160,8 @@ class QPanda3DWidget(QWidget): def resizeEvent(self, evt): width = evt.size().width() height = evt.size().height() - print(f"width:{width}") - print(f"height:{height}") + #print(f"width:{width}") + #print(f"height:{height}") from Panda3DWorld import resize_buffer #resize_buffer(width, height) diff --git a/Resources/models/Women_1.glb b/Resources/models/Women_1.glb new file mode 100644 index 00000000..79ebe8d2 Binary files /dev/null and b/Resources/models/Women_1.glb differ diff --git a/Resources/models/Women_2.glb b/Resources/models/Women_2.glb new file mode 100644 index 00000000..efba7b02 Binary files /dev/null and b/Resources/models/Women_2.glb differ diff --git a/Resources/models/women_1.glb b/Resources/models/women_1.glb new file mode 100644 index 00000000..f634ea92 Binary files /dev/null and b/Resources/models/women_1.glb differ diff --git a/core/InfoPanelManager.py b/core/InfoPanelManager.py index 3083408d..9363d567 100644 --- a/core/InfoPanelManager.py +++ b/core/InfoPanelManager.py @@ -1079,6 +1079,135 @@ class InfoPanelManager(DirectObject): property_panel_instance.createHTTPInfoPanel = types.MethodType(createHTTPInfoPanel, property_panel_instance) property_panel_instance.updateHTTPInfoPanel = types.MethodType(updateHTTPInfoPanel, property_panel_instance) + def serializePanelData(self, panel_id): + """序列化面板数据用于保存""" + if panel_id not in self.panels: + return None + + panel_data = self.panels[panel_id] + props = panel_data['properties'] + + # 获取面板类型 + panel_type = "2d" + if panel_data['node'].hasTag("gui_type"): + gui_type = panel_data['node'].getTag("gui_type") + if "3d" in gui_type.lower(): + panel_type = "3d" + + # 构建序列化数据 + serialized_data = { + 'panel_id': panel_id, + 'panel_type': panel_type, + 'position': props.get('position', (0, 0) if panel_type == "2d" else (0, 0, 0)), + 'size': props.get('size', (1.0, 0.6)), + 'bg_color': props.get('bg_color', (0.15, 0.15, 0.15, 0.9)), + 'border_color': props.get('border_color', (0.3, 0.3, 0.3, 1.0)), + 'title_color': props.get('title_color', (1.0, 1.0, 1.0, 1.0)), + 'content_color': props.get('content_color', (0.9, 0.9, 0.9, 1.0)), + 'title': panel_data['title_label'].getText() if 'title_label' in panel_data else "信息面板", + 'content': panel_data[ + 'content_label'].getText() if 'content_label' in panel_data else "" if 'content_node' not in panel_data else + panel_data['content_node'].getText(), + 'font_path': props.get('font', None), + 'bg_image': props.get('bg_image', None), + 'visible': not panel_data['node'].isHidden() + } + + # 添加HTTP面板特有数据 + if panel_id in self.data_sources and 'http_info' in self.data_sources[panel_id]: + serialized_data['http_info'] = self.data_sources[panel_id]['http_info'] + + return serialized_data + + def getAllPanelData(self): + """获取所有面板的序列化数据""" + panel_data_list = [] + for panel_id in self.panels: + data = self.serializePanelData(panel_id) + if data: + panel_data_list.append(data) + return panel_data_list + + def recreatePanelFromData(self, panel_data): + """从序列化数据重新创建面板""" + try: + panel_id = panel_data['panel_id'] + panel_type = panel_data['panel_type'] + position = panel_data['position'] + size = panel_data['size'] + + # 重建面板 + if panel_type == "3d": + panel_node = self.create3DInfoPanel( + panel_id=panel_id, + position=position, + size=size, + bg_color=panel_data.get('bg_color', (0.15, 0.15, 0.15, 0.9)), + border_color=panel_data.get('border_color', (0.3, 0.3, 0.3, 1.0)), + title_color=panel_data.get('title_color', (1.0, 1.0, 1.0, 1.0)), + content_color=panel_data.get('content_color', (0.9, 0.9, 0.9, 1.0)), + visible=panel_data.get('visible', True), + font=panel_data.get('font_path', None), + bg_image=panel_data.get('bg_image', None) + ) + + # 更新内容 + self.update3DPanelContent( + panel_id, + title=panel_data.get('title', '信息面板'), + content=panel_data.get('content', '') + ) + else: + panel_node = self.createInfoPanel( + panel_id=panel_id, + position=(position[0], position[1]) if len(position) >= 2 else (0, 0), + size=size, + bg_color=panel_data.get('bg_color', (0.15, 0.15, 0.15, 0.9)), + border_color=panel_data.get('border_color', (0.3, 0.3, 0.3, 1.0)), + title_color=panel_data.get('title_color', (1.0, 1.0, 1.0, 1.0)), + content_color=panel_data.get('content_color', (0.9, 0.9, 0.9, 1.0)), + visible=panel_data.get('visible', True), + font=panel_data.get('font_path', None), + bg_image=panel_data.get('bg_image', None) + ) + + # 更新内容 + self.updatePanelContent( + panel_id, + title=panel_data.get('title', '信息面板'), + content=panel_data.get('content', '') + ) + + # 设置标签 + if panel_node: + panel_node.setTag("element_type", "info_panel") + panel_node.setTag("is_scene_element", "1") + panel_node.setTag("supports_3d_position_editing", "1") + + # 如果是HTTP面板,重新注册数据源 + if 'http_info' in panel_data: + http_info = panel_data['http_info'] + self.createHTTPInfoPanel( + panel_id=panel_id, + url=http_info['url'], + method=http_info.get('method', 'GET'), + headers=http_info.get('headers', None), + data=http_info.get('data', None), + position=position, + size=size, + update_interval=self.data_sources.get(panel_id, {}).get('interval', + 30.0) if panel_id in self.data_sources else 30.0 + ) + + print(f"✓ 信息面板 {panel_id} 已重建") + return panel_node + + except Exception as e: + print(f"✗ 重建信息面板 {panel_data.get('panel_id', 'unknown')} 失败: {e}") + import traceback + traceback.print_exc() + return None + # 示例数据源函数 def getRealtimeData(): diff --git a/core/script_system.py b/core/script_system.py index abb206d3..07d6e96e 100644 --- a/core/script_system.py +++ b/core/script_system.py @@ -294,6 +294,31 @@ class ScriptLoader: return len(changed_scripts) > 0 + def find_script_file(self, script_name: str) -> Optional[str]: + """根据脚本名称查找脚本文件路径""" + # 首先检查已加载的脚本 + if script_name in self.loaded_modules: + module = self.loaded_modules[script_name] + if hasattr(module, '__file__') and module.__file__: + return module.__file__ + + # 在已知的文件路径中查找 + for file_path in self.file_mtimes.keys(): + file_name = os.path.splitext(os.path.basename(file_path))[0] + if file_name == script_name: + return file_path + + # 在脚本目录中查找 + scripts_dir = self.script_manager.scripts_directory + if os.path.exists(scripts_dir): + for file_name in os.listdir(scripts_dir): + if file_name.endswith('.py'): + base_name = os.path.splitext(file_name)[0] + if base_name == script_name: + return os.path.join(scripts_dir, file_name) + + return None + class ScriptAPI: """脚本API - 提供给脚本使用的API接口""" diff --git a/core/selection.py b/core/selection.py index f3d87b37..6f160c1e 100644 --- a/core/selection.py +++ b/core/selection.py @@ -212,7 +212,7 @@ class SelectionSystem: return # 获取边界框的最小和最大点(世界坐标) - print(f"世界边界框: min={minPoint}, max={maxPoint}") + #print(f"世界边界框: min={minPoint}, max={maxPoint}") # 创建线段对象 lines = LineSegs() diff --git a/core/terrain_manager.py b/core/terrain_manager.py index 6807c968..edbec218 100644 --- a/core/terrain_manager.py +++ b/core/terrain_manager.py @@ -573,3 +573,88 @@ class TerrainManager: except Exception as e: print(f"应用地形纹理时出错: {e}") return False + + def saveTerrainData(self, terrain_info, filename): + """保存地形数据到文件""" + try: + terrain_node = terrain_info['node'] + heightfield = terrain_info['heightfield'] + + # 保存高度图到文件 + if heightfield: + # 创建保存路径 + terrain_dir = os.path.join(os.path.dirname(filename), "terrains") + if not os.path.exists(terrain_dir): + os.makedirs(terrain_dir) + + # 生成唯一的地形文件名 + terrain_filename = f"terrain_{terrain_info.get('name', 'unnamed')}_{int(time.time())}.png" + terrain_path = os.path.join(terrain_dir, terrain_filename) + + # 保存高度图 + heightfield.write(Filename.fromOsSpecific(terrain_path)) + + # 保存地形信息到标签 + terrain_node.setTag("terrain_heightmap_path", terrain_path) + terrain_node.setTag("terrain_scale", str(terrain_info['scale'])) + + print(f"✓ 地形数据已保存: {terrain_path}") + return terrain_path + return None + except Exception as e: + print(f"保存地形数据时出错: {e}") + return None + + def _recreateTerrain(self, terrain_node): + """重新创建地形""" + try: + print(f"重新创建地形: {terrain_node.getName()}") + + # 获取保存的地形信息 + heightmap_path = terrain_node.getTag("terrain_heightmap_path") if terrain_node.hasTag( + "terrain_heightmap_path") else None + scale_str = terrain_node.getTag("terrain_scale") if terrain_node.hasTag("terrain_scale") else "(1, 1, 1)" + + # 解析缩放信息 + try: + scale_str = scale_str.strip("()") + scale_values = [float(x.strip()) for x in scale_str.split(",")] + scale = (scale_values[0], scale_values[1], scale_values[2]) if len(scale_values) >= 3 else (1, 1, 1) + except: + scale = (1, 1, 1) + + # 恢复位置信息 + if terrain_node.hasTag("transform_pos"): + try: + pos_str = terrain_node.getTag("transform_pos") + pos_str = pos_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()') + pos_values = [float(x.strip()) for x in pos_str.split(',')] + if len(pos_values) >= 3: + terrain_node.setPos(pos_values[0], pos_values[1], pos_values[2]) + except Exception as e: + print(f"恢复地形位置失败: {e}") + + # 如果有高度图路径,重新创建地形 + if heightmap_path and os.path.exists(heightmap_path): + # 使用现有方法重新创建地形 + new_terrain = self.createTerrainFromHeightMap(heightmap_path, scale) + if new_terrain: + # 删除旧的地形节点 + if not terrain_node.isEmpty(): + terrain_node.removeNode() + return new_terrain + + # 如果没有高度图或创建失败,创建一个平面地形作为替代 + print("使用平面地形作为替代") + new_terrain = self.createFlatTerrain(size=(scale[0], scale[1]), resolution=129) + if new_terrain: + # 删除旧的地形节点 + if not terrain_node.isEmpty(): + terrain_node.removeNode() + return new_terrain + + return terrain_node + except Exception as e: + print(f"重新创建地形失败: {e}") + return terrain_node + diff --git a/gui/gui_manager.py b/gui/gui_manager.py index 4fcdf027..83737e6f 100644 --- a/gui/gui_manager.py +++ b/gui/gui_manager.py @@ -126,7 +126,7 @@ class GUIManager: # ==================== GUI元素创建方法 ==================== - def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=0.1): + def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=(0.1,0.1,0.1)): """创建2D GUI按钮 - 支持多选创建和GUI父子关系,优化版本""" try: from direct.gui.DirectGui import DirectButton @@ -182,9 +182,23 @@ class GUIManager: text_font=self.world.getChineseFont() if self.world.getChineseFont() else None, rolloverSound=None, clickSound=None, - parent=parent_gui_node # 设置GUI父节点 + parent=parent_gui_node ) + if not hasattr(button,'_tags'): + button._tags = {} + + # button._tags["gui_type"] = "button" + # button._tags["gui_id"] = f"button_{len(self.gui_elements)}" + # button._tags["gui_text"] = text + # button._tags["is_gui_element"] = "1" + # button._tags["is_scene_element"] = "1" + # button._tags["saved_gui_type"] = "button" + # button._tags["gui_element_type"] = "button" + # button._tags["created_by_user"] = "1" + # button._tags["name"] = button_name + # button.setName(button_name) + # 设置节点标签 button.setTag("gui_type", "button") button.setTag("gui_id", f"button_{len(self.gui_elements)}") @@ -193,7 +207,7 @@ class GUIManager: button.setTag("is_scene_element", "1") # 确保这个标签被设置 button.setTag("tree_item_type", "GUI_BUTTON") button.setTag("saved_gui_type", "button") # 添加这个标签以确保兼容性 - button.setTag("gui_element_type","button") + button.setTag("gui_element_type", "button") button.setTag("created_by_user", "1") button.setTag("gui_parent_type", "gui" if parent_gui_node else "3d") button.setTag("name", button_name) @@ -228,11 +242,11 @@ class GUIManager: return None # 选中最后创建的按钮并更新场景树 - if created_buttons: - last_button, last_qt_item = created_buttons[-1] - if last_qt_item: - tree_widget.setCurrentItem(last_qt_item) - tree_widget.update_selection_and_properties(last_button, last_qt_item) + # if created_buttons: + # last_button, last_qt_item = created_buttons[-1] + # if last_qt_item: + # tree_widget.setCurrentItem(last_qt_item) + # tree_widget.update_selection_and_properties(last_button, last_qt_item) print(f"🎉 总共创建了 {len(created_buttons)} 个GUI按钮") @@ -342,11 +356,11 @@ class GUIManager: return None # 选中最后创建的标签并更新场景树 - if created_labels: - last_label, last_qt_item = created_labels[-1] - if last_qt_item: - tree_widget.setCurrentItem(last_qt_item) - tree_widget.update_selection_and_properties(last_label, last_qt_item) + # if created_labels: + # last_label, last_qt_item = created_labels[-1] + # if last_qt_item: + # tree_widget.setCurrentItem(last_qt_item) + # tree_widget.update_selection_and_properties(last_label, last_qt_item) print(f"🎉 总共创建了 {len(created_labels)} 个GUI标签") @@ -454,11 +468,11 @@ class GUIManager: return None # 选中最后创建的输入框并更新场景树 - if created_entries: - last_entry, last_qt_item = created_entries[-1] - if last_qt_item: - tree_widget.setCurrentItem(last_qt_item) - tree_widget.update_selection_and_properties(last_entry, last_qt_item) + # if created_entries: + # last_entry, last_qt_item = created_entries[-1] + # if last_qt_item: + # tree_widget.setCurrentItem(last_qt_item) + # tree_widget.update_selection_and_properties(last_entry, last_qt_item) print(f"🎉 总共创建了 {len(created_entries)} 个GUI输入框") @@ -535,6 +549,7 @@ class GUIManager: # 如果提供了图像路径,则加载纹理 if image_path: try: + image_node.setTag("image_path", image_path) texture = self.world.loader.loadTexture(image_path) if texture: image_node.setTexture(texture, 1) @@ -587,11 +602,11 @@ class GUIManager: return None # 选中最后创建的按钮并更新场景树 - if created_2dimage: - last_button, last_qt_item = created_2dimage[-1] - if last_qt_item: - tree_widget.setCurrentItem(last_qt_item) - tree_widget.update_selection_and_properties(last_button, last_qt_item) + # if created_2dimage: + # last_button, last_qt_item = created_2dimage[-1] + # if last_qt_item: + # tree_widget.setCurrentItem(last_qt_item) + # tree_widget.update_selection_and_properties(last_button, last_qt_item) print(f"🎉 总共创建了 {len(created_2dimage)} 个GUI按钮") @@ -732,11 +747,11 @@ class GUIManager: return None # 选中最后创建的文本并更新场景树 - if created_texts: - last_text, last_qt_item = created_texts[-1] - if last_qt_item: - tree_widget.setCurrentItem(last_qt_item) - tree_widget.update_selection_and_properties(last_text, last_qt_item) + # if created_texts: + # last_text, last_qt_item = created_texts[-1] + # if last_qt_item: + # tree_widget.setCurrentItem(last_qt_item) + # tree_widget.update_selection_and_properties(last_text, last_qt_item) print(f"🎉 总共创建了 {len(created_texts)} 个3D文本") @@ -840,7 +855,7 @@ class GUIManager: image_node.setTag("gui_type", "3d_image") image_node.setTag("gui_id", f"3d_image_{len(self.gui_elements)}") if image_path: - image_node.setTag("gui_image_path", image_path) + image_node.setTag("image_path", image_path) image_node.setTag("is_gui_element", "1") image_node.setTag("is_scene_element", "1") image_node.setTag("tree_item_type", "GUI_3DIMAGE") @@ -869,11 +884,11 @@ class GUIManager: return None # 选中最后创建的文本并更新场景树 - if created_3dimage: - last_image, last_qt_item = created_3dimage[-1] - if last_qt_item: - tree_widget.setCurrentItem(last_qt_item) - tree_widget.update_selection_and_properties(last_image, last_qt_item) + # if created_3dimage: + # last_image, last_qt_item = created_3dimage[-1] + # if last_qt_item: + # tree_widget.setCurrentItem(last_qt_item) + # tree_widget.update_selection_and_properties(last_image, last_qt_item) print(f"🎉 总共创建了 {len(created_3dimage)} 个3D文本") @@ -889,7 +904,7 @@ class GUIManager: traceback.print_exc() return None - def createVideoScreen(self, pos=(0, 0, 0), size=0.2, video_path=None): + def createVideoScreen(self, pos=(0, 0, 0), size=1, video_path=None): """创建3D视频播放屏幕 - 添加占位符纹理支持""" try: from panda3d.core import CardMaker, TransparencyAttrib, Texture, TextureStage @@ -912,7 +927,7 @@ class GUIManager: size = float(size) except (ValueError, TypeError): print(f"⚠️ 尺寸参数无效,使用默认值 0.2,原始值: {size}") - size = 0.2 + size = 0.2*5 print(f"📺 开始创建视频屏幕,位置: {pos}, 尺寸: {size}, 视频路径: {video_path}") @@ -1060,12 +1075,12 @@ class GUIManager: return None # 选中最后创建的视频屏幕 - if created_videoscreens: - last_screen_np, last_qt_item = created_videoscreens[-1] - if last_qt_item: - tree_widget.setCurrentItem(last_qt_item) - # 更新选择和属性面板 - tree_widget.update_selection_and_properties(last_screen_np, last_qt_item) + # if created_videoscreens: + # last_screen_np, last_qt_item = created_videoscreens[-1] + # if last_qt_item: + # tree_widget.setCurrentItem(last_qt_item) + # # 更新选择和属性面板 + # tree_widget.update_selection_and_properties(last_screen_np, last_qt_item) print(f"🎉 总共创建了 {len(created_videoscreens)} 个视频屏幕") @@ -1143,6 +1158,7 @@ class GUIManager: # 检查是否有播放方法 if hasattr(movie_texture, 'play'): try: + self.loadVideoFile(video_screen, video_screen.getTag("video_path")) movie_texture.play() print(f"▶️ 继续播放视频: {video_screen.getName()}") return True @@ -1153,9 +1169,7 @@ class GUIManager: print(f"⚠️ 纹理对象没有播放方法: {video_screen.getName()}") return False else: - print(f"❌ 视频屏幕没有关联的视频纹理: {video_screen.getName()}") - self._debugVideoScreenTextures(video_screen) - return False + self.loadVideoFile(video_screen,video_screen.getTag("video_path")) except Exception as e: print(f"❌ 播放视频失败: {e}") import traceback @@ -1282,7 +1296,6 @@ class GUIManager: def loadVideoFile(self, video_screen, video_path): """为视频屏幕加载新的视频文件""" try: - from panda3d.core import Texture, TextureStage import os if not os.path.exists(video_path): @@ -1440,8 +1453,6 @@ class GUIManager: print(f"⚠️ 尺寸参数无效,使用默认值 0.2,原始值: {size}, 错误: {e}") size = 0.2 - print(f"📺 开始创建2D视频屏幕,位置: {pos}, 尺寸: {size}, 视频路径: {video_path}") - # 获取树形控件 tree_widget = self._get_tree_widget() if not tree_widget: @@ -1468,7 +1479,7 @@ class GUIManager: frameColor=(1, 1, 1, 1), # 默认背景色 pos=(pos[0] * 0.1, 0, pos[1] * 0.1), # 转换为屏幕坐标 parent=parent_node if tree_widget.is_gui_element(parent_node) else self.world.aspect2d, - suppressMouse=True + suppressMouse=True, ) video_screen.setName(screen_name) @@ -1476,7 +1487,7 @@ class GUIManager: # 设置透明度支持 video_screen.setTransparency(TransparencyAttrib.MAlpha) - # 设置2D视频屏幕特有的标签 + #设置2D视频屏幕特有的标签 video_screen.setTag("gui_type", "2d_video_screen") video_screen.setTag("gui_id", f"2d_video_screen_{len(self.gui_elements)}") video_screen.setTag("gui_text", f"2D视频屏幕_{len(self.gui_elements)}") @@ -1485,11 +1496,8 @@ class GUIManager: video_screen.setTag("tree_item_type", "GUI_2D_VIDEO_SCREEN") video_screen.setTag("created_by_user", "1") - # 设置视频路径标签 - if video_path and os.path.exists(video_path): - video_screen.setTag("video_path", video_path) - else: - video_screen.setTag("video_path", "") + video_screen.setTag("video_path", video_path if video_path else "") + print(f"🔧 设置2D视频屏幕标签 - video_path: {video_path if video_path else '空'}") # 关键修改:预先创建一个占位符纹理,为后续视频播放做准备 placeholder_texture = Texture(f"placeholder_video_texture_{len(self.gui_elements)}") @@ -1562,12 +1570,12 @@ class GUIManager: return None # 选中最后创建的视频屏幕 - if created_videoscreens: - last_screen_np, last_qt_item = created_videoscreens[-1] - if last_qt_item: - tree_widget.setCurrentItem(last_qt_item) - # 更新选择和属性面板 - tree_widget.update_selection_and_properties(last_screen_np, last_qt_item) + # if created_videoscreens: + # last_screen_np, last_qt_item = created_videoscreens[-1] + # if last_qt_item: + # tree_widget.setCurrentItem(last_qt_item) + # # 更新选择和属性面板 + # tree_widget.update_selection_and_properties(last_screen_np, last_qt_item) print(f"🎉 总共创建了 {len(created_videoscreens)} 个2D视频屏幕") @@ -1588,7 +1596,11 @@ class GUIManager: try: import os - if not os.path.exists(video_path): + video_screen.setTag("video_path",video_path) + path = video_screen.getTag("video_path") + print(f"🔧 更新2D视频屏幕标签 - video_path: {path}") + + if not video_path or not os.path.exists(video_path): print(f"❌ 2D视频文件不存在: {video_path}") return False @@ -1600,7 +1612,6 @@ class GUIManager: # 保存视频纹理引用 video_screen.setPythonTag("movie_texture", movie_texture) - video_screen.setTag("video_path", video_path) print(f"✅ 成功加载新2D视频: {video_path}") return True @@ -1828,12 +1839,12 @@ class GUIManager: return None # 选中最后创建的球形视频 - if created_spherical_videos: - last_sphere_np, last_qt_item = created_spherical_videos[-1] - if last_qt_item: - tree_widget.setCurrentItem(last_qt_item) - # 更新选择和属性面板 - tree_widget.update_selection_and_properties(last_sphere_np, last_qt_item) + # if created_spherical_videos: + # last_sphere_np, last_qt_item = created_spherical_videos[-1] + # if last_qt_item: + # tree_widget.setCurrentItem(last_qt_item) + # # 更新选择和属性面板 + # tree_widget.update_selection_and_properties(last_sphere_np, last_qt_item) print(f"🎉 总共创建了 {len(created_spherical_videos)} 个球形视频") @@ -2034,12 +2045,12 @@ class GUIManager: return None # 选中最后创建的虚拟屏幕 - if created_screens: - last_screen_np, last_qt_item = created_screens[-1] - if last_qt_item: - tree_widget.setCurrentItem(last_qt_item) - # 更新选择和属性面板 - tree_widget.update_selection_and_properties(last_screen_np, last_qt_item) + # if created_screens: + # last_screen_np, last_qt_item = created_screens[-1] + # if last_qt_item: + # tree_widget.setCurrentItem(last_qt_item) + # # 更新选择和属性面板 + # tree_widget.update_selection_and_properties(last_screen_np, last_qt_item) print(f"🎉 总共创建了 {len(created_screens)} 个虚拟屏幕") diff --git a/main.py b/main.py index 6b11ce53..9bca08bc 100644 --- a/main.py +++ b/main.py @@ -233,9 +233,9 @@ class MyWorld(CoreWorld): """创建3D图片""" return self.gui_manager.createGUI3DImage(pos,text,size) - def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=0.2): + def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=1): """创建2D GUI图片""" - return self.gui_manager.createGUI2DImage(pos, image_path, size) + return self.gui_manager.createGUI2DImage(pos, image_path, size*0.2) def createVideoScreen(self,pos=(0,0,0),size=1,video_path=None): """创建视频屏幕""" @@ -492,10 +492,6 @@ class MyWorld(CoreWorld): """异步导入模型""" return self.scene_manager.importModelAsync(filepath) - def loadAnimatedModel(self, model_path, anims=None): - """加载带动画的模型""" - return self.scene_manager.loadAnimatedModel(model_path, anims) - # 材质和几何体处理方法 - 代理到scene_manager def processMaterials(self, model): """处理模型材质""" diff --git a/scene/scene_manager.py b/scene/scene_manager.py index 6e21687b..745e6194 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -17,6 +17,7 @@ from panda3d.core import ( import json import aiohttp import asyncio +import inspect from pathlib import Path from panda3d.egg import EggData, EggVertexPool from direct.actor.Actor import Actor @@ -97,6 +98,7 @@ class SceneManager: normalize_scales: 是否标准化子节点缩放(推荐开启) auto_convert_to_glb: 是否自动将非GLB格式转换为GLB以获得更好的动画支持 """ + try: print(f"\n=== 开始导入模型: {filepath} ===") print(f"单位转换: {'开启' if apply_unit_conversion else '关闭'}") @@ -212,6 +214,35 @@ class SceneManager: print(f"导入模型失败: {str(e)}") return None + def _applyModelScale(self, model, scale_factor): + """应用模型特定缩放 + + Args: + model: 要缩放的模型 + scale_factor: 缩放因子 + """ + try: + print(f"应用模型缩放因子: {scale_factor}") + + # 获取当前边界用于后续位置调整 + original_bounds = model.getBounds() + + # 应用缩放 + model.setScale(scale_factor) + + # 重新调整位置(因为缩放会影响边界) + if original_bounds and not original_bounds.isEmpty(): + new_bounds = model.getBounds() + min_point = new_bounds.getMin() + ground_offset = -min_point.getZ() + model.setZ(ground_offset) + print(f"缩放后重新调整位置: Z偏移 = {ground_offset}") + + print(f"模型缩放完成,缩放因子: {scale_factor}") + + except Exception as e: + print(f"应用模型缩放失败: {str(e)}") + def _applyMaterialsToModel(self, model): """递归应用材质到模型的所有GeomNode""" @@ -555,59 +586,6 @@ class SceneManager: except Exception as e: print(f"异步加载模型失败: {str(e)}") - def loadAnimatedModel(self, model_path, anims=None, auto_play=True): - """加载带动画的模型 - - Args: - model_path: 模型文件路径 - anims: 动画字典,格式为 {"动画名": "动画文件路径"} - auto_play: 是否自动播放第一个动画 - """ - try: - print(f"🎬 加载动画模型: {model_path}") - - # 如果没有指定动画,尝试自动检测 - if anims is None: - anims = self._detectAnimations(model_path) - - # 创建Actor对象 - actor = Actor(model_path, anims) - if actor: - actor.reparentTo(self.world.render) - - # 设置碰撞检测 - self.setupCollision(actor) - - # 获取可用的动画列表 - available_anims = actor.getAnimNames() - print(f"📋 检测到动画: {available_anims}") - - # 自动播放第一个动画 - if auto_play and available_anims: - first_anim = available_anims[0] - actor.loop(first_anim) - print(f"▶️ 自动播放动画: {first_anim}") - - # 添加动画控制标签 - actor.setTag("animated", "true") - actor.setTag("current_anim", first_anim) - actor.setTag("available_anims", str(available_anims)) - - # 调整模型位置(让它站在地面上) - self._adjustModelPosition(actor) - - self.models.append(actor) - # 更新场景树 - self.updateSceneTree() - - print(f"✅ 动画模型加载完成: {actor.getName()}") - return actor - except Exception as e: - print(f"❌ 加载动画模型失败: {str(e)}") - import traceback - traceback.print_exc() - return None - # ==================== 材质和几何体处理 ==================== def processMaterials(self, model): @@ -725,8 +703,195 @@ class SceneManager: # ==================== 场景保存和加载 ==================== + def _collectGUIElementInfo(self, gui_node): + """收集GUI元素的信息用于保存""" + try: + # 获取GUI元素类型 + gui_type = "unknown" + if hasattr(gui_node, 'hasTag') and gui_node.hasTag("gui_type"): + gui_type = gui_node.getTag("gui_type") + elif hasattr(gui_node, 'hasTag') and gui_node.hasTag("saved_gui_type"): + gui_type = gui_node.getTag("saved_gui_type") + else: + # 尝试从节点名称推断类型 + name_lower = gui_node.getName().lower() + if "button" in name_lower: + gui_type = "button" + elif "label" in name_lower: + gui_type = "label" + elif "entry" in name_lower: + gui_type = "entry" + elif "image" in name_lower: + gui_type = "2d_image" + elif "videoscreen" in name_lower: + if "2d" in name_lower: + gui_type = "2d_video_screen" + else: + gui_type = "video_screen" + else: + # 如果无法识别类型,跳过该元素 + print(f"跳过无法识别类型的GUI元素: {gui_node.getName()}") + return None + + gui_info = { + "name": gui_node.getName(), + "type": gui_type, + "position": list(gui_node.getPos()), + "rotation": list(gui_node.getHpr()), + "scale": list(gui_node.getScale()), + "tags": {} + } + + # 收集所有标签(仅对NodePath类型的对象) + if hasattr(gui_node, 'getTagNames'): + for tag in gui_node.getTagNames(): + gui_info["tags"][tag] = gui_node.getTag(tag) + elif hasattr(gui_node, 'getTags'): # 对于DirectGUI对象 + # DirectGUI对象使用不同的方法存储标签 + if hasattr(gui_node, '_tags'): + gui_info["tags"] = gui_node._tags.copy() + + # 根据类型收集特定信息 + if gui_type == "button": + if hasattr(gui_node, 'get'): # DirectButton + gui_info["text"] = gui_node.get() + elif hasattr(gui_node, 'getText'): # 其他类型 + gui_info["text"] = gui_node.getText() + elif hasattr(gui_node, 'hasTag') and gui_node.hasTag("gui_text"): + gui_info["text"] = gui_node.getTag("gui_text") + elif gui_type == "label": + if hasattr(gui_node, 'getText'): + gui_info["text"] = gui_node.getText() + elif hasattr(gui_node, 'hasTag') and gui_node.hasTag("gui_text"): + gui_info["text"] = gui_node.getTag("gui_text") + elif gui_type == "entry": + if hasattr(gui_node, 'get'): + gui_info["text"] = gui_node.get() + elif hasattr(gui_node, 'hasTag') and gui_node.hasTag("gui_text"): + gui_info["text"] = gui_node.getTag("gui_text") + elif gui_type == "2d_image": + if hasattr(gui_node, 'hasTag') and gui_node.hasTag("image_path"): + gui_info["image_path"] = gui_node.getTag("image_path") + elif hasattr(gui_node, 'hasTag') and gui_node.hasTag("gui_image_path"): + gui_info["image_path"] = gui_node.getTag("gui_image_path") + elif gui_type == "3d_text": + if hasattr(gui_node,'hasTag') and gui_node.hasTag("gui_text"): + gui_info["text"] = gui_node.getTag("gui_text") + elif hasattr(gui_node,'node') and hasattr(gui_node.node(),'getText'): + gui_info["text"] = gui_node.node().getText() + elif gui_type == "3d_image": + if hasattr(gui_node,'hasTag') and gui_node.hasTag("gui_image_path"): + gui_info["image_path"] = gui_node.getTag("gui_image_path") + elif gui_type in ["video_screen", "2d_video_screen"]: + if hasattr(gui_node, 'hasTag') and gui_node.hasTag("video_path"): + gui_info["video_path"] = gui_node.getTag("video_path") + gui_info["video_path"] = gui_node.getTag("video_path") + elif gui_type == "virtual_screen": + if hasattr(gui_node, 'hasTag') and gui_node.hasTag("gui_text"): + gui_info["text"] = gui_node.getTag("gui_text") + elif gui_type == "info_panel": + if hasattr(gui_node, 'hasTag') and gui_node.hasTag("panel_data"): + gui_info["panel_data"] = gui_node.getTag("panel_data") + + # 修改 _collectGUIElementInfo 方法中的脚本收集部分 + if hasattr(self.world, 'script_manager') and self.world.script_manager: + script_manager = self.world.script_manager + # 获取挂载在此节点上的所有脚本 + scripts = script_manager.get_scripts_on_object(gui_node) + if scripts: + gui_info["scripts"] = [] + for script_component in scripts: + script_name = script_component.script_name # 使用脚本组件的名称 + print(f"收集脚本信息: {script_name}") + + # 获取脚本类的文件路径 + script_class = script_component.script_instance.__class__ + script_file = self._get_script_file_path(script_class, script_name) + + gui_info["scripts"].append({ + "name": script_name, + "file": script_file + }) + print(f"脚本 {script_name} 来自文件: {script_file}") + + print(f"成功收集GUI元素信息: {gui_info}") + return gui_info + except Exception as e: + print(f"收集GUI元素信息失败: {e}") + import traceback + traceback.print_exc() + return None + + def _get_script_file_path(self, script_class, script_name): + """ + 获取脚本文件路径的可靠方法 + """ + script_file = "" + + # 方法1: 使用 inspect.getfile + try: + script_file = inspect.getfile(script_class) + if script_file and os.path.exists(script_file): + return script_file + except: + pass + + # 方法2: 使用 __file__ 属性 + try: + if hasattr(script_class, '__file__') and script_class.__file__: + script_file = script_class.__file__ + if script_file and os.path.exists(script_file): + return script_file + except: + pass + + # 方法3: 使用模块的 __file__ 属性 + try: + module = inspect.getmodule(script_class) + if module and hasattr(module, '__file__') and module.__file__: + script_file = module.__file__ + if script_file and os.path.exists(script_file): + return script_file + except: + pass + + # 方法4: 从脚本管理器中查找 + try: + if hasattr(self.world, 'script_manager') and self.world.script_manager: + script_manager = self.world.script_manager + # 查找脚本类对应的文件路径 + for file_path, file_mtime in script_manager.loader.file_mtimes.items(): + # 检查文件名是否匹配脚本名 + file_name = os.path.splitext(os.path.basename(file_path))[0] + if file_name == script_name: + if os.path.exists(file_path): + return file_path + except: + pass + + # 方法5: 在脚本目录中查找 + try: + if hasattr(self.world, 'script_manager') and self.world.script_manager: + script_manager = self.world.script_manager + scripts_dir = script_manager.scripts_directory + + # 查找匹配的脚本文件 + if os.path.exists(scripts_dir): + for file_name in os.listdir(scripts_dir): + if file_name.endswith('.py'): + base_name = os.path.splitext(file_name)[0] + if base_name == script_name: + full_path = os.path.join(scripts_dir, file_name) + if os.path.exists(full_path): + return full_path + except: + pass + + print(f"警告: 无法获取脚本 {script_name} 的文件路径") + return script_file + def saveScene(self, filename, project_path): - """保存场景到BAM文件 - 完整版,支持GUI元素""" + """保存场景到BAM文件 - 完整版,支持GUI元素,地形""" try: print(f"\n=== 开始保存场景到: {filename} ===") @@ -765,7 +930,7 @@ class SceneManager: all_nodes.extend(self.Spotlight) all_nodes.extend(self.Pointlight) - # 添加GUI元素节点(关键修改) + # 添加GUI元素节点 gui_elements = [] if hasattr(self.world, 'gui_elements'): # 过滤掉空的或重复的GUI元素 @@ -774,14 +939,36 @@ class SceneManager: for elem in self.world.gui_elements: if elem and not elem.isEmpty(): - elem_name = elem.getName() if hasattr(elem, 'getName') else str(id(elem)) - if elem_name not in seen_names: + if not elem.isEmpty() and elem.getName() not in seen_names: unique_gui_elements.append(elem) - seen_names.add(elem_name) - + seen_names.add(elem.getName()) gui_elements = unique_gui_elements - all_nodes.extend(gui_elements) - print(f"找到 {len(gui_elements)} 个唯一的GUI元素待保存") + + print(f"保存时GUI元素列表=>>>>>>>>>>>>{self.world.gui_elements}") + all_nodes.extend(gui_elements) + + # 创建用于保存GUI信息的JSON文件路径 + gui_info_file = filename.replace('.bam', '_gui.json') + + print(self.world.gui_elements) + # 收集GUI元素信息(排除3D文本和3D图像) + gui_data = [] + for gui_node in gui_elements: + gui_info = self._collectGUIElementInfo(gui_node) + if gui_info: + gui_data.append(gui_info) + print(f"添加GUI信息{gui_info['name']}") + + # 保存GUI信息到JSON文件(确保即使没有GUI元素也创建有效的空JSON数组) + try: + import json + with open(gui_info_file, 'w', encoding='utf-8') as f: + json.dump(gui_data, f, ensure_ascii=False, indent=2) + print(f"✓ GUI信息已保存到: {gui_info_file}") + except Exception as e: + print(f"✗ 保存GUI信息失败: {e}") + import traceback + traceback.print_exc() # 添加tilesets节点 for tileset_info in self.tilesets: @@ -793,7 +980,6 @@ class SceneManager: if tileset_info.get('node') and not tileset_info['node'].isEmpty(): all_nodes.append(tileset_info['node']) - # 保存所有节点的信息 for node in all_nodes: if node.isEmpty(): @@ -822,12 +1008,6 @@ class SceneManager: if material.hasBaseColor(): node.setTag("material_basecolor", str(material.getBaseColor())) - # 如果有颜色属性,保存为标签 - # if state.hasAttrib(ColorAttrib.getClassType()): - # color_attrib = state.getAttrib(ColorAttrib.getClassType()) - # if not color_attrib.isOff(): - # node.setTag("color", str(color_attrib.getColor())) - # 保存特定类型节点的额外信息 if node.hasTag("light_type"): # 保存光源特定信息 @@ -849,14 +1029,6 @@ class SceneManager: node.getTag("saved_gui_type") if node.hasTag("saved_gui_type") else "unknown" node.setTag("saved_gui_type", gui_type) - # 特别记录3D文本的内容 - if gui_type == "3d_text": - text_content = "未知内容" - if node.hasTag("gui_text"): - text_content = node.getTag("gui_text") - elif hasattr(node.node(), 'getText'): - text_content = node.node().getText() - print(f"保存3D文本信息: {node.getName()} (内容: '{text_content}')") # 保存GUI元素的通用属性 if hasattr(node, 'getPythonTag'): @@ -867,8 +1039,15 @@ class SceneManager: node.setTag(f"python_tag_{tag_name}", str(tag_value)) except: pass - print(f"保存GUI元素信息: {node.getName()} (类型: {gui_type})") - print(f"11111111111111{self.world.gui_elements}") + elif node.hasTag("element_type") and node.getTag("element_type") == "info_panel": + # 保存信息面板特定信息 + print(f"保存信息面板信息: {node.getName()}") + panel_id = node.getTag("panel_id") if node.hasTag("panel_id") else node.getName() + if hasattr(self.world, 'info_panel_manager'): + panel_data = self.world.info_panel_manager.serializePanelData(panel_id) + if panel_data: + import json + node.setTag("info_panel_data", json.dumps(panel_data, ensure_ascii=False)) try: print("--- 打印当前场景图 (render) ---") @@ -878,7 +1057,6 @@ class SceneManager: self.take_screenshot(project_path) # 保存场景 success = self.world.render.writeBamFile(Filename.fromOsSpecific(filename)) - #success_2d = self.world.render2d.writeBamFile(Filename.fromOsSpecific(filename)) if success: print(f"✓ 场景保存成功: {filename}") @@ -950,7 +1128,7 @@ class SceneManager: return False def loadScene(self, filename): - """从BAM文件加载场景 - 修复版""" + """从BAM文件加载场景""" try: print(f"\n=== 开始加载场景: {filename} ===") @@ -1009,6 +1187,8 @@ class SceneManager: gui.removeNode() self.world.gui_elements.clear() + if hasattr(self.world,'info_panel_manager'): + self.world.info_panel_manager.removeAllPanels() # 清理可能存在的辅助节点 self._cleanupAuxiliaryNodes() @@ -1024,7 +1204,6 @@ class SceneManager: # 用于存储处理后的灯光节点,避免重复处理 processed_lights = [] # 用于存储处理后的GUI元素,避免重复处理 - processed_gui_elements = [] # 遍历场景中的所有节点 def processNode(nodePath, depth=0): @@ -1063,7 +1242,8 @@ class SceneManager: nodePath.hasTag("light_type") or nodePath.hasTag("gui_type") or # 检查gui_type标签 nodePath.hasTag("is_gui_element") or - nodePath.hasTag("saved_gui_type") + nodePath.hasTag("saved_gui_type") or + (nodePath.hasTag("element_type") and nodePath.getTag("element_type") == "info_panel") ) # 特殊处理:检查节点名称是否包含GUI相关关键词 @@ -1197,52 +1377,6 @@ class SceneManager: self.tilesets.append(tileset_info) self.cesium_integration.tilesets[nodePath.getName()] = tileset_info - elif nodePath.hasTag("gui_type") or nodePath.hasTag("is_gui_element") or nodePath.hasTag( - "saved_gui_type"): - # 获取GUI元素类型 - gui_type = "unknown" - if nodePath.hasTag("gui_type"): - gui_type = nodePath.getTag("gui_type") - elif nodePath.hasTag("saved_gui_type"): - gui_type = nodePath.getTag("saved_gui_type") - else: - gui_type = "unknown_gui_element" - - print(f"{indent}检测到GUI元素类型: {gui_type}") - - # 检查是否已经处理过这个GUI元素 - if nodePath not in processed_gui_elements: - # 重新创建GUI元素 - recreate_success = self._recreateGUIElement(nodePath) - - # 确保GUI元素被正确标记 - if not nodePath.hasTag("is_scene_element"): - nodePath.setTag("is_scene_element", "1") - - # 检查是否已经存在于gui_elements列表中,避免重复添加 - already_in_list = False - if hasattr(self.world, 'gui_elements'): - # 检查节点是否已经在列表中(通过名称或对象引用) - for existing_gui in self.world.gui_elements: - if existing_gui == nodePath or (hasattr(existing_gui, 'getName') and - hasattr(nodePath, 'getName') and - existing_gui.getName() == nodePath.getName()): - already_in_list = True - break - - # 只有当元素不在列表中时才添加 - if not already_in_list: - if hasattr(self.world, 'gui_elements'): - self.world.gui_elements.append(nodePath) - print( - f"{indent}GUI元素已添加到管理列表,当前列表大小: {len(self.world.gui_elements)}") - else: - print(f"{indent}GUI元素已存在于管理列表中,跳过添加") - - # 标记为已处理 - processed_gui_elements.append(nodePath) - print(f"{indent}GUI元素已处理并标记") - # 将节点重新挂载到render下(如果需要) # 注意:GUI元素可能需要挂载到特定的父节点上 if nodePath.hasTag("gui_type") or nodePath.hasTag("is_gui_element"): @@ -1268,6 +1402,31 @@ class SceneManager: print("\n开始处理场景节点...") processNode(scene) + # 加载GUI信息并重新创建非3D的GUI元素 + gui_info_file = filename.replace('.bam', '_gui.json') + if os.path.exists(gui_info_file): + try: + with open(gui_info_file, 'r', encoding='utf-8') as f: + content = f.read().strip() + if content: # 检查文件是否为空 + import json + gui_data = json.loads(content) + print(f"✓ 成功加载GUI信息文件: {gui_info_file}") + print(f" 发现 {len(gui_data)} 个GUI元素需要重建") + + # 使用gui_manager重新创建GUI元素 + self._recreateGUIElementsFromData(gui_data) + else: + print("ℹ️ GUI信息文件为空") + except json.JSONDecodeError as e: + print(f"✗ GUI信息文件格式错误: {e}") + except Exception as e: + print(f"✗ 加载GUI信息失败: {e}") + import traceback + traceback.print_exc() + else: + print("ℹ️ 未找到GUI信息文件") + # 移除临时场景节点 if not scene.isEmpty(): scene.removeNode() @@ -1292,360 +1451,232 @@ class SceneManager: traceback.print_exc() return False - def _recreate3DText(self, text_node): - """重新创建3D文本元素 - 使用gui_manager中的createGUI3DText方法""" + def _recreateGUIElementsFromData(self, gui_data): + """根据保存的GUI数据重新创建GUI元素""" try: - print(f"重新创建3D文本: {text_node.getName()}") + gui_manager = getattr(self.world, 'gui_manager', None) + if not gui_manager: + print("GUI管理器未找到,无法重建GUI元素") + return - # 获取保存的文本内容 - text_content = "默认文本" - if text_node.hasTag("gui_text"): - text_content = text_node.getTag("gui_text") - elif text_node.hasTag("text"): - text_content = text_node.getTag("text") + print(f"开始重建 {len(gui_data)} 个GUI元素...") - print(f"3D文本内容: {text_content}") + # 用于跟踪已处理的元素名称,防止重复创建 + processed_names = set() - # 获取位置 - pos = (0, 0, 0) - if text_node.hasTag("transform_pos"): + for i, gui_info in enumerate(gui_data): try: - pos_str = text_node.getTag("transform_pos") - pos_str = pos_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()') - pos_values = [float(x.strip()) for x in pos_str.split(',')] - if len(pos_values) >= 3: - pos = (pos_values[0], pos_values[1], pos_values[2]) - except Exception as e: - print(f"恢复位置失败: {e}") + gui_type = gui_info.get("type", "unknown") + name = gui_info.get("name", f"gui_element_{i}") + position = gui_info.get("position", [0, 0, 0]) + scale = gui_info.get("scale", [1, 1, 1]) + tags = gui_info.get("tags", {}) + text = gui_info.get("text", "") + image_path = gui_info.get("image_path", "") + video_path = gui_info.get("video_path","") - # 获取尺寸 - size = 0.5 - if text_node.hasTag("transform_scale"): - try: - scale_str = text_node.getTag("transform_scale") - scale_str = scale_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()') - scales = [float(x.strip()) for x in scale_str.split(',')] - size = scales[0] if scales else 0.5 - except Exception as e: - print(f"恢复缩放失败: {e}") + # 检查是否已经处理过同名元素 + if name in processed_names: + print(f"跳过重复元素: {name}") + continue - # 使用gui_manager中的createGUI3DText方法创建新的3D文本 - # 首先需要找到gui_manager - gui_manager = None - if hasattr(self.world, 'gui_manager'): - gui_manager = self.world.gui_manager + processed_names.add(name) - if gui_manager and hasattr(gui_manager, 'createGUI3DText'): - # 记录创建前的GUI元素数量 - original_count = len(gui_manager.gui_elements) + print(f"重建GUI元素: {name} (类型: {gui_type})") + print(f" 位置: {position}") + print(f" 缩放: {scale}") + print(f" 文本: {text}") + print(f" 图像路径: {image_path}") + print(f"视频路径:{video_path}") - # 调用gui_manager的createGUI3DText方法创建新的3D文本 - new_text_node = gui_manager.createGUI3DText(pos=pos, text=text_content, size=size) + # 根据类型创建相应的GUI元素 + new_element = None - if new_text_node: - # 如果返回的是列表(多选创建),取第一个 - if isinstance(new_text_node, list): - created_node = new_text_node[0] + if gui_type == "button" and hasattr(gui_manager, 'createGUIButton'): + new_element = gui_manager.createGUIButton( + pos=tuple(position), + text=text, + size=scale[0] if scale and len(scale) > 0 else 1.0 + ) + elif gui_type == "label" and hasattr(gui_manager, 'createGUILabel'): + scale_value = scale[0] if scale and len(scale) > 0 else 1.0 + new_element = gui_manager.createGUILabel( + pos=tuple(position), + text=text, + size=scale_value + ) + elif gui_type == "entry" and hasattr(gui_manager, 'createGUIEntry'): + new_element = gui_manager.createGUIEntry( + pos=tuple(position), + placeholder=text, + size=scale[0] if scale and len(scale) > 0 else 1.0 + ) + elif gui_type == "2d_image" and hasattr(gui_manager, 'createGUI2DImage'): + scale_value = scale[0] if scale and len(scale) > 0 else 0.2 + new_element = gui_manager.createGUI2DImage( + pos=tuple(position), + image_path=image_path, + size=scale_value*0.2 + ) + elif gui_type == "3d_text" and hasattr(gui_manager,'createGUI3DText'): + size = scale[0] if scale and len(scale) > 0 else 0.5 + new_element = gui_manager.createGUI3DText( + pos=tuple(position), + text=text, + size=size + ) + elif gui_type == "3d_image" and hasattr(gui_manager, 'createGUI3DImage'): + # 处理3D图像 + # 根据缩放值的数量处理尺寸 + if len(scale) >= 3: + size = (scale[0] * 2, scale[1] * 2) + elif len(scale) >= 2: + size = (scale[0] * 2, scale[1] * 2) + elif len(scale) >= 1: + size = (scale[0] * 2, scale[0] * 2) + else: + size = (1.0, 1.0) + + new_element = gui_manager.createGUI3DImage( + pos=tuple(position), + image_path=image_path, + size=size + ) + elif gui_type == "video_screen" and hasattr(gui_manager,'createVideoScreen'): + new_element = gui_manager.createVideoScreen( + pos=tuple(position), + size=scale, + video_path=video_path + ) + if video_path and new_element and hasattr(gui_manager, 'loadVideoFile'): + # 延迟一帧执行,确保节点完全初始化 + from direct.task.TaskManagerGlobal import taskMgr + def load_video_task(task): + gui_manager.loadVideoFile(new_element, video_path) + return task.done + + taskMgr.doMethodLater(0.1, load_video_task, 'loadVideoTask') + + elif gui_type == "2d_video_screen" and hasattr(gui_manager,'createGUI2DVideoScreen'): + new_element = gui_manager.createGUI2DVideoScreen( + pos=tuple(position), + size=scale, + video_path=video_path + ) + + + # 如果创建成功,设置属性 + if new_element: + # 如果返回的是列表(多选创建),取第一个 + if isinstance(new_element, list): + new_element = new_element[0] + + # 设置名称 + new_element.setName(name) + + # 设置变换 + new_element.setPos(*position) + + if len(scale) >= 3: + new_element.setScale(scale[0], scale[1], scale[2]) + elif len(scale) >= 1: + new_element.setScale(scale[0]) + + # 设置标签 + # 对于NodePath对象 + if hasattr(new_element, 'setTag'): + for tag_name, tag_value in tags.items(): + # 跳过变换标签,因为我们已经设置了 + if tag_name not in ["transform_pos", "transform_hpr", "transform_scale"]: + new_element.setTag(tag_name, tag_value) + # 对于DirectGUI对象,使用自定义标签存储 + elif hasattr(new_element, '_tags'): + new_element._tags.update(tags) + + # 重新挂载脚本(如果有的话) + # 在 _recreateGUIElementsFromData 方法中找到重新挂载脚本的部分,替换为以下代码: + + # 重新挂载脚本(如果有的话) + if "scripts" in gui_info and hasattr(self.world, + 'script_manager') and self.world.script_manager: + script_manager = self.world.script_manager + for script_info in gui_info["scripts"]: + script_name = script_info["name"] + script_file = script_info.get("file", "") + + print(f"尝试重新挂载脚本: {script_name} from {script_file}") + + # 检查脚本是否已加载 + if script_name not in script_manager.loader.script_classes: + # 如果脚本未加载,尝试从保存的文件路径加载 + if script_file and os.path.exists(script_file): + print(f"从文件加载脚本: {script_file}") + loaded_class = script_manager.load_script_from_file(script_file) + if loaded_class is None: + print(f"从文件加载脚本失败: {script_file}") + # 如果从文件加载失败,尝试在脚本目录中查找 + script_path = self._find_script_in_directory(script_name) + if script_path: + print(f"从目录找到脚本并加载: {script_path}") + script_manager.load_script_from_file(script_path) + else: + # 如果没有文件路径或文件不存在,尝试在脚本目录中查找 + script_path = self._find_script_in_directory(script_name) + if script_path: + print(f"从目录找到脚本并加载: {script_path}") + script_manager.load_script_from_file(script_path) + else: + print(f"找不到脚本文件: {script_name}") + + # 为元素添加脚本 + script_component = script_manager.add_script_to_object(new_element, script_name) + if script_component: + print(f"成功为 {name} 添加脚本: {script_name}") + else: + print(f"为 {name} 添加脚本失败: {script_name}") + + print(f"GUI元素重建成功: {name}") else: - created_node = new_text_node + print(f"无法重建GUI元素: {name} (类型: {gui_type})") - print(f"3D文本重建完成: {created_node.getName()}, 内容: '{text_content}'") - return created_node - else: - print("gui_manager.createGUI3DText返回None") + except Exception as e: + print(f"重建GUI元素失败 {name}: {e}") + import traceback + traceback.print_exc() + continue + + print("GUI元素重建完成") except Exception as e: - print(f"重新创建3D文本失败: {str(e)}") + print(f"重建GUI元素时发生错误: {e}") import traceback traceback.print_exc() - return None - def _recreateGUIElement(self, gui_node): - """重新创建GUI元素的通用方法""" + def _find_script_in_directory(self, script_name): + """在脚本目录中查找脚本文件""" try: - # 获取GUI元素类型 - gui_type = "unknown" - if gui_node.hasTag("gui_type"): - gui_type = gui_node.getTag("gui_type") - elif gui_node.hasTag("saved_gui_type"): - gui_type = gui_node.getTag("saved_gui_type") - else: - gui_type = "unknown_gui_element" + if hasattr(self.world, 'script_manager') and self.world.script_manager: + script_manager = self.world.script_manager + scripts_dir = script_manager.scripts_directory - print(f"重新创建GUI元素: {gui_node.getName()} (类型: {gui_type})") + if os.path.exists(scripts_dir): + # 首先精确匹配 + for file_name in os.listdir(scripts_dir): + if file_name.endswith('.py'): + base_name = os.path.splitext(file_name)[0] + if base_name == script_name: + return os.path.join(scripts_dir, file_name) - # 根据类型重新创建GUI元素 - recreated_node = None - if gui_type == "3d_text": - recreated_node = self._recreate3DText(gui_node) - elif gui_type == "3d_image": - recreated_node = self._recreate3DImage(gui_node) - # 可以在这里添加其他类型的GUI元素重建逻辑 - else: - # 对于其他类型的GUI元素,使用备用方法 - recreated_node = self._recreateGUIElementFallback(gui_node, gui_type) - - # 如果没有特定的重建方法,至少确保基本属性 - target_node = recreated_node if recreated_node else gui_node - - # 确保节点被正确标记 - if not target_node.hasTag("is_scene_element"): - target_node.setTag("is_scene_element", "1") - - # 注意:这里不再直接添加到gui_elements列表,因为gui_manager.createGUI3DText已经添加了 - # 只有在使用备用方法且元素不在列表中时才添加 - - # 恢复GUI特定属性 - self._restoreGUIProperties(target_node) - - print(f"GUI元素 {target_node.getName()} 重建完成") - return True + # 如果没有精确匹配,尝试模糊匹配 + for file_name in os.listdir(scripts_dir): + if file_name.endswith('.py'): + base_name = os.path.splitext(file_name)[0] + if script_name.lower() in base_name.lower() or base_name.lower() in script_name.lower(): + return os.path.join(scripts_dir, file_name) except Exception as e: - print(f"重新创建GUI元素失败: {str(e)}") - return False - def _recreate3DImage(self, image_node): - """重新创建3D图像元素""" - try: - from panda3d.core import Texture, TextureStage - from panda3d.core import CardMaker, Material, LColor, TransparencyAttrib + print(f"查找脚本文件时出错: {e}") - print(f"重新创建3D图像: {image_node.getName()}") - - # 获取保存的属性 - image_path = image_node.getTag("gui_image_path") if image_node.hasTag("gui_image_path") else None - - # 获取位置 - pos = (0, 0, 0) - if image_node.hasTag("transform_pos"): - try: - pos_str = image_node.getTag("transform_pos") - pos_str = pos_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()') - pos_values = [float(x.strip()) for x in pos_str.split(',')] - if len(pos_values) >= 3: - pos = (pos_values[0], pos_values[1], pos_values[2]) - except Exception as e: - print(f"解析位置失败: {e}") - - # 获取尺寸 - size = 1.0 - if image_node.hasTag("transform_scale"): - try: - scale_str = image_node.getTag("transform_scale") - scale_str = scale_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()') - scales = [float(x.strip()) for x in scale_str.split(',')] - size = scales[0] if scales else 1.0 - except Exception as e: - print(f"解析尺寸失败: {e}") - - # 创建卡片 - cm = CardMaker('gui_3d_image_recreated') - x_size = y_size = float(size) - cm.setFrame(-x_size / 2, x_size / 2, -y_size / 2, y_size / 2) - - # 创建3D图像节点 - new_image_node = image_node.getParent().attachNewNode(cm.generate()) - new_image_node.setName(image_node.getName()) - new_image_node.setPos(*pos) - - # 设置材质 - material = Material(f"image-material-recreated-{len(getattr(self, '_recreated_image_count', [0]))}") - material.setBaseColor(LColor(1, 1, 1, 1)) - material.setDiffuse(LColor(1, 1, 1, 1)) - material.setAmbient(LColor(0.5, 0.5, 0.5, 1)) - material.setSpecular(LColor(0.1, 0.1, 0.1, 1.0)) - material.setShininess(10.0) - material.setEmission(LColor(0, 0, 0, 1)) - new_image_node.setMaterial(material, 1) - - new_image_node.setTransparency(TransparencyAttrib.MAlpha) - - # 如果有图像路径,加载纹理 - if image_path: - try: - texture = self.world.loader.loadTexture(image_path) - if texture: - new_image_node.setTexture(texture, 1) - texture.setWrapU(Texture.WM_clamp) - texture.setWrapV(Texture.WM_clamp) - texture.setMinfilter(Texture.FT_linear) - texture.setMagfilter(Texture.FT_linear) - except Exception as e: - print(f"加载纹理失败: {e}") - - # 设置标签 - new_image_node.setTag("gui_type", "3d_image") - new_image_node.setTag("gui_id", image_node.getTag("gui_id") if image_node.hasTag( - "gui_id") else f"3d_image_{len(getattr(self.world, 'gui_elements', []))}") - if image_path: - new_image_node.setTag("gui_image_path", image_path) - new_image_node.setTag("is_gui_element", "1") - new_image_node.setTag("is_scene_element", "1") - new_image_node.setTag("created_by_user", "1") - - # 复制变换标签 - for tag in ["transform_pos", "transform_hpr", "transform_scale"]: - if image_node.hasTag(tag): - new_image_node.setTag(tag, image_node.getTag(tag)) - - # 添加到GUI元素列表 - if hasattr(self.world, 'gui_elements'): - if new_image_node not in self.world.gui_elements: - self.world.gui_elements.append(new_image_node) - - print(f"3D图像重建完成: {new_image_node.getName()}") - return new_image_node - - except Exception as e: - print(f"重新创建3D图像失败: {str(e)}") - import traceback - traceback.print_exc() - return None - - def _recreateGUIElement(self, gui_node): - """重新创建GUI元素的通用方法""" - try: - # 获取GUI元素类型 - gui_type = "unknown" - if gui_node.hasTag("gui_type"): - gui_type = gui_node.getTag("gui_type") - elif gui_node.hasTag("saved_gui_type"): - gui_type = gui_node.getTag("saved_gui_type") - else: - gui_type = "unknown_gui_element" - - print(f"重新创建GUI元素: {gui_node.getName()} (类型: {gui_type})") - - # 根据类型重新创建GUI元素 - recreated_node = None - if gui_type == "3d_text": - recreated_node = self._recreate3DText(gui_node) - elif gui_type == "3d_image": - recreated_node = self._recreate3DImage(gui_node) - # 可以在这里添加其他类型的GUI元素重建逻辑 - else: - # 对于其他类型的GUI元素,使用备用方法 - recreated_node = self._recreateGUIElementFallback(gui_node, gui_type) - - # 如果没有特定的重建方法,至少确保基本属性 - target_node = recreated_node if recreated_node else gui_node - - # 确保节点被正确标记 - if not target_node.hasTag("is_scene_element"): - target_node.setTag("is_scene_element", "1") - - # 添加到GUI元素列表(如果还没有添加) - if hasattr(self.world, 'gui_elements'): - if target_node not in self.world.gui_elements: - self.world.gui_elements.append(target_node) - - # 恢复GUI特定属性 - self._restoreGUIProperties(target_node) - - print(f"GUI元素 {target_node.getName()} 重建完成") - return True - except Exception as e: - print(f"重新创建GUI元素失败: {str(e)}") - return False - - def _recreateGUIElementFallback(self, gui_node, gui_type): - """GUI元素重建的备用方法""" - try: - # 对于3D文本,确保使用正确的重建方法 - if gui_type == "3d_text": - return self._recreate3DText(gui_node) - - # 对于其他类型,保持原有节点但确保标签正确 - # 确保必要的标签 - required_tags = { - "gui_type": gui_type, - "is_gui_element": "1", - "is_scene_element": "1", - "created_by_user": "1" - } - - for tag, value in required_tags.items(): - if not gui_node.hasTag(tag): - gui_node.setTag(tag, value) - - return gui_node - except Exception as e: - print(f"GUI元素备用重建失败: {str(e)}") - return gui_node - - def _restoreGUIProperties(self, gui_node): - """恢复GUI元素的特定属性""" - try: - # 恢复文本内容(对于文本相关的GUI元素) - if gui_node.hasTag("gui_text"): - text_content = gui_node.getTag("gui_text") - # 如果节点有setText方法,则设置文本 - if hasattr(gui_node, 'setText'): - gui_node.setText(text_content) - # 如果是TextNode,则设置文本 - elif hasattr(gui_node, 'node') and hasattr(gui_node.node(), 'setText'): - gui_node.node().setText(text_content) - - # 恢复大小 - if gui_node.hasTag("gui_size"): - try: - size_str = gui_node.getTag("gui_size") - # 解析并设置大小 - pass # 根据具体需要实现 - except: - pass - - # 恢复颜色 - if gui_node.hasTag("gui_color"): - try: - color_str = gui_node.getTag("gui_color") - # 解析并设置颜色 - pass # 根据具体需要实现 - except: - pass - - print(f"已恢复GUI元素 {gui_node.getName()} 的属性") - except Exception as e: - print(f"恢复GUI属性时出错: {str(e)}") - - def createGUIElement(self, element_type, name=None, **kwargs): - """创建GUI元素的通用方法""" - try: - from panda3d.core import NodePath - - # 生成唯一名称 - if name is None: - name = f"{element_type}_{len(getattr(self.world, 'gui_elements', []))}" - - # 创建GUI元素节点 - element_node = NodePath(name) - - # 设置GUI元素标签 - element_node.setTag("is_gui_element", "1") - element_node.setTag("gui_type", element_type) - element_node.setTag("is_scene_element", "1") - element_node.setTag("saved_gui_type", element_type) - - # 添加到场景 - element_node.reparentTo(self.world.render) - - # 添加到GUI元素列表 - if not hasattr(self.world, 'gui_elements'): - self.world.gui_elements = [] - self.world.gui_elements.append(element_node) - print(f"1111111111111111111{self.world.gui_elements}") - - # 保存额外属性 - for key, value in kwargs.items(): - element_node.setTag(f"gui_{key}", str(value)) - - # 更新场景树 - self.updateSceneTree() - - print(f"创建GUI元素: {name} (类型: {element_type})") - return element_node - except Exception as e: - print(f"创建GUI元素失败: {str(e)}") - return None + return None def _recreateSpotLight(self, light_node): """重新创建聚光灯""" @@ -2050,92 +2081,6 @@ class SceneManager: pass return None - def _importModelSingle(self, filepath, apply_unit_conversion=False, normalize_scales=True, auto_convert_to_glb=True): - """传统单一模型导入方法(兼容性保留)""" - try: - print(f"\n=== 使用传统模式导入模型: {filepath} ===") - - filepath = util.normalize_model_path(filepath) - original_filepath = filepath - - # 检查是否需要转换为GLB - if auto_convert_to_glb and self._shouldConvertToGLB(filepath): - print(f"🔄 检测到需要转换的格式,尝试转换为GLB...") - converted_path = self._convertToGLBWithProgress(filepath) - if converted_path: - print(f"✅ 转换成功: {converted_path}") - filepath = converted_path - try: - from PyQt5.QtWidgets import QMessageBox - original_ext = os.path.splitext(original_filepath)[1].upper() - QMessageBox.information(None, "转换成功", - f"已将 {original_ext} 格式自动转换为 GLB 格式\n以获得更好的动画支持!") - except: - pass - else: - print(f"⚠️ 转换失败,使用原始文件") - - # 加载模型 - print("直接从文件加载模型...") - model = self.world.loader.loadModel(filepath) - if not model: - print("加载模型失败") - return None - - # 设置模型名称 - model_name = os.path.basename(filepath) - model.setName(model_name) - - # 将模型添加到场景 - model.reparentTo(self.world.render) - - # 设置标签和路径信息 - model.setTag("model_path", filepath) - model.setTag("original_path", original_filepath) - model.setTag("file", model_name) - model.setTag("is_model_root", "1") - model.setTag("is_scene_element", "1") - model.setTag("created_by_user", "1") - - if filepath != original_filepath: - model.setTag("converted_from", os.path.splitext(original_filepath)[1]) - model.setTag("converted_to_glb", "true") - - # 应用处理选项 - if apply_unit_conversion and filepath.lower().endswith('.fbx'): - print("应用FBX单位转换(厘米到米)...") - self._applyUnitConversion(model, 0.01) - model.setTag("unit_conversion_applied", "true") - - if normalize_scales and filepath.lower().endswith('.fbx'): - print("标准化FBX模型缩放层级...") - self._normalizeModelScales(model) - model.setTag("scale_normalization_applied", "true") - - # 调整模型位置到地面 - self._adjustModelToGround(model) - - # 创建并设置基础材质 - print("\n=== 开始设置材质 ===") - self._applyMaterialsToModel(model) - - # 设置碰撞检测(重要!用于选择功能) - print("\n=== 设置碰撞检测 ===") - self.setupCollision(model) - - # 添加到模型列表 - self.models.append(model) - - # 更新场景树 - self.updateSceneTree() - - print(f"=== 模型导入成功: {model_name} ===\n") - return model - - except Exception as e: - print(f"导入模型失败: {str(e)}") - return None - # def createSpotLight(self, pos=(0, 0, 0)): # """创建聚光灯 - 使用统一的create_item方法""" # try: diff --git a/threejs_panel.html b/threejs_panel.html new file mode 100644 index 00000000..ee6ef4c1 --- /dev/null +++ b/threejs_panel.html @@ -0,0 +1,106 @@ + + + + + Three.js Panel + + + +
+

场景信息面板

+

FPS: 0

+

对象数: 0

+
+
+ + + + + diff --git a/ui/interface_manager.py b/ui/interface_manager.py index da36ce6d..17042936 100644 --- a/ui/interface_manager.py +++ b/ui/interface_manager.py @@ -296,7 +296,6 @@ class InterfaceManager: addNodeToTree(model, sceneRoot,force=True) # 添加所有GUI元素 - print(f"GUIGUIGUIGUIGUIGUIGUIGUIGUIGUIGUIGUIUGIUGI{self.world.gui_elements}") for gui in self.world.gui_elements: # 检查是否是有效的GUI节点(具有getTag方法的NodePath) if hasattr(gui, 'getTag') and hasattr(gui, 'getName'): diff --git a/ui/main_window.py b/ui/main_window.py index 66f5b9ff..92af669d 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -1072,6 +1072,7 @@ class MainWindow(QMainWindow): # self.bottomDock.setWidget(self.fileView) # self.addDockWidget(Qt.BottomDockWidgetArea, self.bottomDock) + # 创建底部停靠窗口(资源窗口) self.bottomDock = QDockWidget("资源", self) self.bottomDock.setStyleSheet(""" diff --git a/ui/property_panel.py b/ui/property_panel.py index a943f54a..d7262ee6 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -14,7 +14,7 @@ from direct.actor.Actor import Actor from direct.gui import DirectGui from idna import check_label from jinja2.compiler import has_safe_repr -from panda3d.core import Vec3, Vec4, transpose, TransparencyAttrib, PartGroup, ColorAttrib +from panda3d.core import Vec3, Vec4, transpose, TransparencyAttrib, PartGroup, ColorAttrib, NodePath from scene import util from direct.gui.DirectGui import DirectLabel, DirectFrame from panda3d.core import TextNode @@ -1690,9 +1690,9 @@ class PropertyPanelManager: # 添加弹性空间 if gui_type == "video_screen": - self._addVideoScreenProperties(gui_element) + self._addVideoScreenProperties(gui_element,item) elif gui_type == "2d_video_screen": - self._add2DVideoScreenProperties(gui_element) + self._add2DVideoScreenProperties(gui_element,item) elif gui_type == "spherical_video": self._addSphericalVideoProperties(gui_element) elif gui_type in ['info_panel','info_panel_3d']: @@ -3292,7 +3292,7 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _addVideoScreenProperties(self, video_screen): + def _addVideoScreenProperties(self, video_screen,item): """添加视频屏幕属性面板""" try: from PyQt5.QtWidgets import (QGroupBox, QGridLayout, QPushButton, QLabel, @@ -3359,7 +3359,7 @@ class PropertyPanelManager: # 加载新视频按钮 load_btn = QPushButton("📁 加载新视频...") - load_btn.clicked.connect(lambda: self._loadNewVideo(video_screen)) + load_btn.clicked.connect(lambda: self._loadNewVideo(video_screen,item)) self._propertyLayout.addWidget(load_btn) # 添加URL输入区域 @@ -3382,7 +3382,7 @@ class PropertyPanelManager: except Exception as e: print(f"添加视频屏幕属性失败: {e}") - def _add2DVideoScreenProperties(self, video_screen): + def _add2DVideoScreenProperties(self, video_screen,item): """为2D视频屏幕添加属性控制面板""" try: from PyQt5.QtWidgets import (QGroupBox, QGridLayout, QPushButton, QLabel, @@ -3453,7 +3453,7 @@ class PropertyPanelManager: # 加载新视频按钮 load_btn = QPushButton("📁 加载新视频...") - load_btn.clicked.connect(lambda: self._loadNew2DVideo(video_screen)) + load_btn.clicked.connect(lambda: self._loadNew2DVideo(video_screen,item)) self._propertyLayout.addWidget(load_btn) # 添加URL输入区域 @@ -3751,7 +3751,7 @@ class PropertyPanelManager: except Exception as e: print(f"❌ 停止视频失败: {e}") return False - def _loadNew2DVideo(self, video_screen): + def _loadNew2DVideo(self, video_screen,item): """为2D视频屏幕加载新视频文件""" try: file_path, _ = QFileDialog.getOpenFileName( @@ -3766,60 +3766,12 @@ class PropertyPanelManager: print(f"成功加载新视频: {file_path}") # 刷新属性面板以显示新视频信息 self._stop2DVideo(video_screen) - self.updateGUIPropertyPanel(video_screen) + self.updateGUIPropertyPanel(video_screen,item) return True except Exception as e: print(f"加载新视频失败: {e}") return False - def load2DVideoFile(self, video_screen, video_path): - """为2D视频屏幕加载新的视频文件""" - try: - import os - - # 处理空路径情况 - 显示空白 - if not video_path or video_path == "": - print("ℹ️ 空视频路径,显示空白") - video_screen["frameColor"] = (0, 0, 0, 0) # 透明背景 - video_screen.clearPythonTag("movie_texture") - video_screen.setTag("video_path", "") - return True - - # 检查文件是否存在 - if not os.path.exists(video_path): - print(f"❌ 2D视频文件不存在: {video_path}") - # 显示空白而不是红色错误提示 - video_screen["frameColor"] = (0, 0, 0, 0) # 透明背景 - return False - - # 加载新的视频纹理 - movie_texture = self._loadMovieTexture(video_path) - if movie_texture: - # 应用纹理到2D视频屏幕 - video_screen["frameTexture"] = movie_texture - # 设置白色背景以正确显示视频 - video_screen["frameColor"] = (1, 1, 1, 1) - - # 保存视频纹理引用 - video_screen.setPythonTag("movie_texture", movie_texture) - video_screen.setTag("video_path", video_path) - - print(f"✅ 成功加载新2D视频: {video_path}") - return True - else: - print(f"❌ 无法加载2D视频文件: {video_path}") - # 显示空白而不是红色错误提示 - video_screen["frameColor"] = (0, 0, 0, 0) # 透明背景 - return False - - except Exception as e: - print(f"❌ 加载2D视频文件失败: {e}") - import traceback - traceback.print_exc() - # 显示空白而不是红色错误提示 - video_screen["frameColor"] = (0, 0, 0, 0) # 透明背景 - return False - def _loadVideoFromURLWithOpenCV_3D(self, video_screen, url): """使用OpenCV从URL加载视频流并在3D视频屏幕上显示""" try: @@ -4077,7 +4029,7 @@ class PropertyPanelManager: return False - def _loadNewVideo(self,video_screen): + def _loadNewVideo(self,video_screen,item): try: file_path, _ = QFileDialog.getOpenFileName( None, @@ -4088,8 +4040,7 @@ class PropertyPanelManager: if file_path: success = self.world.gui_manager.loadVideoFile(video_screen,file_path) if success: - print(f"成功加载新视频{file_path}") - self.updateGUIPropertyPanel(video_screen) + self.updateGUIPropertyPanel(video_screen,item) return True except Exception as e: print(f"加载新视频失败{e}") @@ -8222,17 +8173,19 @@ class PropertyPanelManager: # 其他格式使用标准 Actor 加载 try: - test_actor=Actor(filepath) + import gltf + print(f"[GLTF加载] 尝试加载: {filepath}") + # test_actor=Actor(NodePath(gltf._loader.GltfLoader.load_file(filepath,None))) + test_actor=Actor(NodePath(gltf.load_model(filepath,None))) anims = test_actor.getAnimNames() + test_actor.reparentTo(self.world.render) + self._actor_cache[origin_model] = test_actor print(f"[Actor加载] 标准加载检测到动画: {anims}") if not anims: test_actor.cleanup() test_actor.removeNode() return None - actor = Actor(filepath) - actor.reparentTo(self.world.render) - self._actor_cache[origin_model] = actor - return actor + return test_actor except Exception as e: print(f"创建Actor失败: {e}") return None @@ -8588,6 +8541,8 @@ except Exception as e: print(f"[系统转换] 系统转换失败: {e}") return None + + def _playAnimation(self,origin_model): actor=self._getActor(origin_model) if not actor: @@ -8700,7 +8655,7 @@ except Exception as e: anim_name = self.animation_combo.currentText() actor.setPos(origin_model.getPos()) actor.setHpr(origin_model.getHpr()) - actor.setScale(origin_model.getScale()) + actor.setScale(origin_model.getScale()/100) if cmd == "play": origin_model.hide()