#!/usr/bin/env python # -*- coding: utf-8 -*- import os import sys import maya.cmds as cmds import maya.OpenMayaUI as omui from shiboken2 import wrapInstance import traceback # 添加项目根目录到 Python 路径 ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) if ROOT_DIR not in sys.path: sys.path.insert(0, ROOT_DIR) from config import data from dna_utils import DNAManager QtCore, QtGui, QtWidgets = data.Qt() #===================================== 2. Global Variables ===================================== TOOL_NAME = data.TOOL_NAME TOOL_VERSION = data.TOOL_VERSION TOOL_AUTHOR = data.TOOL_AUTHOR TOOL_LANG = data.TOOL_LANG TOOL_WSCL_NAME = data.TOOL_WSCL_NAME TOOL_HELP_URL = data.TOOL_HELP_URL SCRIPTS_PATH = data.SCRIPTS_PATH ICONS_PATH = data.ICONS_PATH TOOL_MAIN_SCRIPT = data.TOOL_MAIN_SCRIPT TOOL_MOD_FILENAME = data.TOOL_MOD_FILENAME TOOL_ICON = data.TOOL_ICON TOOL_COMMAND_ICON = data.TOOL_COMMAND_ICON DNA_PATH = data.DNA_PATH DNA_IMG_PATH = data.DNA_IMG_PATH DNALIB_PATH = data.DNALIB_PATH BUILDER_PATH = data.BUILDER_PATH main_window = None class ModelTab(QtWidgets.QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QtWidgets.QVBoxLayout(self) # LOD导航栏 self.lod_tabs = QtWidgets.QTabWidget() for i in range(1, 7): tab = QtWidgets.QWidget() self.lod_tabs.addTab(tab, f"LOD{i-1}") layout.addWidget(self.lod_tabs) # 模型操作工具栏 tool_bar = QtWidgets.QHBoxLayout() self.add_button(tool_bar, "导入模型", "import.png", self.import_model) self.add_button(tool_bar, "导出模型", "export.png", self.export_model) layout.addLayout(tool_bar) def add_button(self, layout, text, icon, callback): btn = QtWidgets.QPushButton(QtGui.QIcon(os.path.join(data.ICONS_PATH, icon)), text) btn.clicked.connect(callback) layout.addWidget(btn) def import_model(self): pass def export_model(self): pass class RigTab(QtWidgets.QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal) # 左侧控制器列表 left_panel = QtWidgets.QWidget() left_layout = QtWidgets.QVBoxLayout(left_panel) self.controller_list = QtWidgets.QListWidget() left_layout.addWidget(self.controller_list) # 右侧视口区域 right_panel = QtWidgets.QWidget() right_layout = QtWidgets.QVBoxLayout(right_panel) self.viewport_label = QtWidgets.QLabel("3D视口区域") right_layout.addWidget(self.viewport_label) splitter.addWidget(left_panel) splitter.addWidget(right_panel) main_layout = QtWidgets.QHBoxLayout(self) main_layout.addWidget(splitter) class AdjustTab(QtWidgets.QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QtWidgets.QVBoxLayout(self) layout.addWidget(QtWidgets.QLabel("调整功能开发中...")) class DefineTab(QtWidgets.QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QtWidgets.QVBoxLayout(self) layout.addWidget(QtWidgets.QLabel("定义功能开发中...")) class MainWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setup_core_components() def setup_core_components(self): """初始化核心组件""" self.setup_paths() self.init_ui() self.setup_connections() def setup_paths(self): """配置系统路径""" # 添加插件路径 if data.PLUGIN_PATH not in sys.path: sys.path.insert(0, data.PLUGIN_PATH) # 添加PyDNA二进制路径到环境变量 pydna_bin = os.path.join(data.PYDNA_PATH, "bin") os.environ["PATH"] = f"{pydna_bin}{os.pathsep}{os.environ['PATH']}" def init_ui(self): """初始化UI框架""" self.setWindowTitle(f"{data.TOOL_NAME} {data.TOOL_VERSION}") self.setMinimumSize(1200, 800) # 主窗口布局 main_widget = QtWidgets.QWidget() self.setCentralWidget(main_widget) main_layout = QtWidgets.QVBoxLayout(main_widget) # 创建核心组件 self.create_menu_bar() self.create_toolbar() self.create_tab_widget() # 加载样式 self.load_styles() def load_styles(self): """加载样式表""" style_path = os.path.join(data.STYLES_PATH, "style.qss") if os.path.exists(style_path): with open(style_path, "r", encoding="utf-8") as f: self.setStyleSheet(f.read()) def create_menu_bar(self): """创建菜单栏""" menubar = self.menuBar() # 文件菜单 file_menu = menubar.addMenu("文件") self.add_menu_action(file_menu, "打开DNA", "open.png", lambda: self.load_dna()) self.add_menu_action(file_menu, "保存DNA", "save.png", lambda: self.save_dna()) self.add_menu_action(file_menu, "加载当前项目的DNA", "open.png", self.on_load_project_dna) self.add_menu_action(file_menu, "修改混合目标名称", "rename.png", self.on_rename_blend_target) self.add_menu_action(file_menu, "重置混合目标名称", "resetname.png", self.on_reset_blend_target) self.add_menu_action(file_menu, "导出FBX", "export.png", self.on_export_fbx) file_menu.addSeparator() self.add_menu_action(file_menu, "退出", "exit.png", self.close) # 编辑菜单 edit_menu = menubar.addMenu("编辑") self.add_menu_action(edit_menu, "创建RL4节点", "connect.png", self.create_rl4_node) self.add_menu_action(edit_menu, "删除RL4节点", "disconnect.png", self.delete_rl4_node) self.add_menu_action(edit_menu, "镜像左至右", "mirrorL.png", self.mirror_left_to_right) self.add_menu_action(edit_menu, "镜像右至左", "mirrorR.png", self.mirror_right_to_left) self.add_menu_action(edit_menu, "姿势由A型转T型", "pose_A_To_T.png", self.pose_A_to_T) self.add_menu_action(edit_menu, "姿势由T型转A型", "pose_T_To_A.png", self.pose_T_to_A) self.add_menu_action(edit_menu, "传输LOD贴图", "locator.png", self.transfer_lod_texture) self.add_menu_action(edit_menu, "设置关节颜色", "color.png", self.set_joint_color) self.add_menu_action(edit_menu, "取消全部标记", "unmark_all.png", self.unmark_all) self.add_menu_action(edit_menu, "重建所有目标", "rebuildTargets.png", self.rebuild_targets) self.add_menu_action(edit_menu, "为所有表情设置关键帧", "bakeAnimation.png", self.bake_all_animations) self.add_menu_action(edit_menu, "烘焙所有表情的关键帧", "centerCurrentTime.png", self.bake_all_keyframes) # 工具菜单 tool_menu = menubar.addMenu("工具") self.add_menu_action(tool_menu, "导出蒙皮", "export_skin.png", self.export_skin) self.add_menu_action(tool_menu, "导入蒙皮", "import_skin.png", self.import_skin) self.add_menu_action(tool_menu, "拷贝装皮", "copy_skin.png", self.copy_skin) self.add_menu_action(tool_menu, "RBF变形器", "blendShape.png", self.create_blend_shape) self.add_menu_action(tool_menu, "快速绑定服装", "clothing_weight.png", self.quick_bind_clothing) self.add_menu_action(tool_menu, "克隆混合变形", "blendShape.png", self.clone_blend_shape) self.add_menu_action(tool_menu, "UV传递点序", "repair_vertex_order.png", self.repair_vertex_order) self.add_menu_action(tool_menu, "面部生成控制器", "controller.png", self.create_face_controller) self.add_menu_action(tool_menu, "提取52BS", "ARKit52.png", self.extract_52BS) self.add_menu_action(tool_menu, "关节轴向修复", "joint.png", self.repair_joint_axis) self.add_menu_action(tool_menu, "生成身体控制器", "create_body_ctrl.png", self.create_body_controller) self.add_menu_action(tool_menu, "导入面部动画", "import_face_anim.png", self.import_face_animation) self.add_menu_action(tool_menu, "导入身体动画", "import_body_anim.png", self.import_body_animation) # 语言菜单 lang_menu = menubar.addMenu("语言") self.add_menu_action(lang_menu, "中文", "chinese.png", self.set_chinese) self.add_menu_action(lang_menu, "English", "english.png", self.set_english) # 帮助菜单 help_menu = menubar.addMenu("帮助") self.add_menu_action(help_menu, "帮助文档", "help.png", self.show_help_document) self.add_menu_action(help_menu, "关于", "warning.png", self.show_about_dialog) def add_menu_action(self, menu, text, icon, callback): """通用菜单项添加方法""" action = QtWidgets.QAction(text, self) if icon: # 优先使用Maya内置图标 maya_icon = self.get_maya_icon(icon) if maya_icon: action.setIcon(maya_icon) else: # 使用自定义图标 icon_path = os.path.join(data.ICONS_PATH, icon) if os.path.exists(icon_path): action.setIcon(QtGui.QIcon(icon_path)) else: cmds.warning(f"图标文件不存在: {icon_path}") action.triggered.connect(callback) menu.addAction(action) def get_maya_icon(self, icon_name): """获取Maya内置图标""" maya_icons = { "bakeAnimation.png": "BakeSimulation.png", "centerCurrentTime.png": "timeCurrentFrame.png", "export_skin.png": "kinReroot.png", # 添加更多图标映射... } if icon_name in maya_icons: return QtGui.QIcon(f":/{maya_icons[icon_name]}") return None def create_toolbar(self): """创建工具栏""" toolbar = self.addToolBar("主工具栏") toolbar.setMovable(False) # 添加工具栏按钮 toolbar.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "save.png")), "保存DNA") toolbar.addAction(QtGui.QIcon(os.path.join(ICONS_PATH, "open.png")), "加载当前项目DNA") def create_tab_widget(self): """创建主标签页""" self.tab_widget = QtWidgets.QTabWidget() self.centralWidget().layout().addWidget(self.tab_widget) # 初始化各标签页 self.model_tab = ModelTab() self.rig_tab = RigTab() self.adjust_tab = AdjustTab() self.define_tab = DefineTab() # 添加标签页 self.tab_widget.addTab(self.model_tab, "模型") self.tab_widget.addTab(self.rig_tab, "绑定") self.tab_widget.addTab(self.adjust_tab, "调整") self.tab_widget.addTab(self.define_tab, "定义") def setup_connections(self): """建立信号连接""" # 示例:连接标签页切换信号 self.tab_widget.currentChanged.connect(self.on_tab_changed) def on_tab_changed(self, index): """标签页切换回调""" current_tab = self.tab_widget.widget(index) print(f"切换到标签页: {current_tab.objectName()}") def setup_model_tab(self): """设置模型标签页内容""" layout = QtWidgets.QVBoxLayout(self.model_tab) # 创建 LOD 分栏 self.lod_tabs = QtWidgets.QTabWidget() layout.addWidget(self.lod_tabs) # 添加 LOD 分页 for i in range(8): lod_tab = QtWidgets.QWidget() lod_layout = QtWidgets.QVBoxLayout(lod_tab) # 添加删除按钮 delete_btn = QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "delete.png")), "删除") lod_layout.addWidget(delete_btn) # 添加模型输入框和加载按钮 for part in ["头部", "牙齿", "牙龈", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "软骨", "身体"]: part_layout = QtWidgets.QHBoxLayout() part_input = QtWidgets.QLineEdit() load_btn = QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "target.png")), "加载") part_layout.addWidget(part_input) part_layout.addWidget(load_btn) lod_layout.addLayout(part_layout) self.lod_tabs.addTab(lod_tab, f"LOD{i}") # 添加 LOD 功能按钮 lod_func_layout = QtWidgets.QHBoxLayout() lod_func_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "load_meshes.png")), "自定加载模型")) lod_func_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "standardized_naming.png")), "标准化命名")) lod_func_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "automatic_groupingg.png")), "自动分组")) layout.addLayout(lod_func_layout) # 添加模型工具 model_tool_layout = QtWidgets.QHBoxLayout() model_tool_layout.addWidget(QtWidgets.QComboBox()) # 拓扑结构下拉菜单 model_tool_layout.addWidget(QtWidgets.QComboBox()) # 选择LOD下拉菜单 model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "polySplitVertex.png")), "模型分离")) model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "supplement_meshes.png")), "生成面部配件")) model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "repair_normals.png")), "修复法线")) model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "repair_vertex_order.png")), "修复点序")) model_tool_layout.addWidget(QtWidgets.QPushButton(QtGui.QIcon(os.path.join(ICONS_PATH, "polyChipOff.png")), "修复接缝")) layout.addLayout(model_tool_layout) def setup_rig_tab(self): """设置绑定标签页内容""" layout = QtWidgets.QVBoxLayout(self.rig_tab) # 添加 DNA 浏览器 self.dna_browser = QtWidgets.QListWidget() self.dna_browser.setIconSize(QtCore.QSize(64, 64)) layout.addWidget(self.dna_browser) # 添加 DNA 图标缩放滑块 self.dna_scale_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) self.dna_scale_slider.setMinimum(50) self.dna_scale_slider.setMaximum(200) self.dna_scale_slider.setValue(100) self.dna_scale_slider.valueChanged.connect(self.on_dna_scale_changed) layout.addWidget(self.dna_scale_slider) # 添加导入/导出按钮 button_layout = QtWidgets.QHBoxLayout() import_btn = QtWidgets.QPushButton("导入设置") export_btn = QtWidgets.QPushButton("导出设置") import_btn.clicked.connect(self.on_import_settings) export_btn.clicked.connect(self.on_export_settings) button_layout.addWidget(import_btn) button_layout.addWidget(export_btn) layout.addLayout(button_layout) # 初始化 DNA 浏览器 self.init_dna_browser() def init_dna_browser(self): """初始化 DNA 浏览器""" # 清空现有内容 self.dna_browser.clear() # 获取 DNA 文件列表 dna_files = os.listdir(DNA_PATH) img_files = os.listdir(DNA_IMG_PATH) # 添加 DNA 项目 for dna_file in dna_files: item = QtWidgets.QListWidgetItem(dna_file) # 查找对应的图片 img_name = os.path.splitext(dna_file)[0] + ".png" if img_name in img_files: item.setIcon(QtGui.QIcon(os.path.join(data.DNA_IMG_PATH, img_name))) self.dna_browser.addItem(item) def on_dna_scale_changed(self, value): """处理 DNA 图标缩放""" size = int(64 * (value / 100)) self.dna_browser.setIconSize(QtCore.QSize(size, size)) def on_import_settings(self): pass def on_export_settings(self): """导出设置""" # TODO: 实现导出设置功能 pass def setup_adjust_tab(self): """设置调整标签页内容""" layout = QtWidgets.QVBoxLayout(self.adjust_tab) # 添加BlendShape列表 blend_list = QtWidgets.QListWidget() layout.addWidget(blend_list) # 添加滑块组 slider_layout = QtWidgets.QVBoxLayout() for i in range(5): slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) slider_layout.addWidget(slider) layout.addLayout(slider_layout) # 添加按钮组 button_layout = QtWidgets.QHBoxLayout() edit_btn = QtWidgets.QPushButton("编辑BlendShape") save_btn = QtWidgets.QPushButton("保存设置") button_layout.addWidget(edit_btn) button_layout.addWidget(save_btn) layout.addLayout(button_layout) def setup_define_tab(self): """设置定义标签页内容""" layout = QtWidgets.QVBoxLayout(self.define_tab) # 添加DNA编辑区域 self.dna_edit = QtWidgets.QTextEdit() layout.addWidget(self.dna_edit) # 添加按钮组 button_layout = QtWidgets.QHBoxLayout() self.load_btn = QtWidgets.QPushButton("载入DNA") self.save_btn = QtWidgets.QPushButton("保存DNA") self.export_btn = QtWidgets.QPushButton("导出FBX") # 连接信号槽 self.load_btn.clicked.connect(self.on_load_dna) self.save_btn.clicked.connect(self.on_save_dna) self.export_btn.clicked.connect(self.on_export_fbx) button_layout.addWidget(self.load_btn) button_layout.addWidget(self.save_btn) button_layout.addWidget(self.export_btn) layout.addLayout(button_layout) # 创建DNA管理器 self.dna_manager = DNAManager() def on_load_dna(self): pass def on_save_dna(self): pass def on_export_fbx(self): pass def on_open_dna(self): pass def on_load_project_dna(self): pass def on_rename_blend_target(self): pass def on_reset_blend_target(self): pass def create_rl4_node(self): pass def delete_rl4_node(self): pass def mirror_left_to_right(self): pass def mirror_right_to_left(self): pass def pose_A_to_T(self): pass def pose_T_to_A(self): pass def transfer_lod_texture(self): pass def set_joint_color(self): pass def unmark_all(self): pass def rebuild_targets(self): pass def bake_all_animations(self): pass def bake_all_keyframes(self): pass def safe_shutdown(self): pass def export_skin(self): pass def import_skin(self): pass def copy_skin(self): pass def create_blend_shape(self): pass def quick_bind_clothing(self): pass def clone_blend_shape(self): pass def repair_vertex_order(self): pass def create_face_controller(self): pass def extract_52BS(self): pass def repair_joint_axis(self): pass def create_body_controller(self): pass def import_face_animation(self): pass def import_body_animation(self): pass # 添加语言支持方法 def set_chinese(self): """设置中文界面""" print("切换中文界面(待实现)") # TODO: 实现国际化切换 def set_english(self): """设置英文界面""" print("切换英文界面(待实现)") # TODO: 实现国际化切换 # 添加帮助相关方法 def show_help_document(self): """显示帮助文档""" print("打开帮助文档(待实现)") # TODO: 实现帮助文档打开逻辑 def show_about_dialog(self): """显示关于对话框""" print("显示关于信息(待实现)") # TODO: 实现关于对话框 # ===================================== 显示主窗口 ===================================== def get_maya_window(): """获取 Maya 主窗口""" import maya.OpenMayaUI as omui from shiboken2 import wrapInstance maya_main_window_ptr = omui.MQtUtil.mainWindow() return wrapInstance(int(maya_main_window_ptr), QtWidgets.QWidget) def dock_to_maya(): """将窗口嵌入到 Maya 的 Dock 面板""" try: # 先清理可能存在的旧控件 if cmds.workspaceControl(TOOL_WSCL_NAME, exists=True): cmds.deleteUI(TOOL_WSCL_NAME) # 创建新的Dock控件 dock_control = cmds.workspaceControl( TOOL_WSCL_NAME, label=TOOL_NAME, tabToControl=["AttributeEditor", -1], initialWidth=1400, minimumWidth=1000, minimumHeight=800, widthProperty="free", heightProperty="free" ) # 获取Dock控件指针 maya_dock_ptr = omui.MQtUtil.findControl(dock_control) if not maya_dock_ptr: raise RuntimeError("无法获取Dock控件指针") # 获取Maya主窗口并创建实例 maya_main_window = get_maya_window() main_window = MainWindow(parent=maya_main_window) # 嵌入到Dock maya_dock_widget = wrapInstance(int(maya_dock_ptr), QtWidgets.QWidget) maya_dock_widget.layout().addWidget(main_window) return main_window except Exception as e: error_msg = f"Dock嵌入失败: {str(e)}\n{''.join(traceback.format_exc())}" cmds.warning(error_msg) return None def show(): """显示主窗口""" global main_window try: # 清理旧实例 if main_window: main_window.close() cmds.deleteUI(TOOL_WSCL_NAME) except: pass # 尝试嵌入 Dock main_window = dock_to_maya() # 备用方案:独立窗口模式 if not main_window: cmds.warning("Dock 模式失败,使用独立窗口模式") main_window = MainWindow() main_window.show() return main_window # ===================================== 主函数 ===================================== if __name__ == "__main__": app = QtWidgets.QApplication([]) window = show() app.exec_()