This commit is contained in:
2025-05-08 00:39:41 +08:00
parent 1bc836fafa
commit 24acc2a6f1
17 changed files with 1464 additions and 1388 deletions

View File

@@ -1,21 +1,28 @@
## Global rules:
# WindSurf Rules
## Global rules
- Comprehensive search: All questions must first retrieve all relevant documents in the project folder to obtain context.
- Web search: After retrieving local files, a web search must be conducted to obtain more information and the latest data.
- Use context7 as much as possible to reduce tokens
## MCP usage:
## MCP usage rules
Use the following MCP model context protocol to enhance responses:
1. markitdown mcp:
1. markitdown mcp
- Purpose: Convert PDF files to Markdown format.
- Trigger conditions: Used when the user query contains information in the PDF file, or needs to convert the PDF content to a more readable format.
2. sequential-thinking mcp;
2. sequential-thinking mcp
- Purpose: Break down complex problems into smaller, more manageable parts, and reason step by step to ensure the logic and coherence of the answer.
- Trigger: Used when the user asks a question that requires multiple steps or relies on logical reasoning. This helps provide a clearer and more organized response.
## Dialogue rules:
## Dialogue rules
- Always respond in 中文 in Cascade chat an composer dialogue bar
## Code rules:
## Code rules
- Code are always in English
- Comments are always in English
- Check code errors and correct them after each code update
@@ -24,13 +31,15 @@ Use the following MCP model context protocol to enhance responses:
- The code must be compatible with Maya2022, Maya2023, Maya2024, Maya2025
- The code must be compatible with Python3.10, Python3.11, Python3.12
- All codes must be encoded in UTF-8
- All modules must be preceded by:
- All modules must be preceded by
```.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
```
## Code write steps rules:
## Code write steps rules
- Read https://github.com/EpicGames/MetaHuman-DNA-Calibration, to understand the code examples and basic rules of Metahuman DNA calibration, use context7;
- Read https://epicgames.github.io/MetaHuman-DNA-Calibration/index.html, to understand the code examples and basic rules of Metahuman DNA calibration, use context7;
- Read Readme.md to understand the plugin goals
@@ -39,9 +48,11 @@ Use the following MCP model context protocol to enhance responses:
- All UIs must be implemented using Qtscripts\Qt.py
- After each function is implemented, the document must be updated
## Project rules:
### UI Styles:
- Flat design style,
## Project rules
### UI Styles
- Flat design style
- It should have a sense of luxury and sci-fi futurism
- Rounded corner design
- Chinese should be clear and elegant
@@ -53,36 +64,138 @@ Use the following MCP model context protocol to enhance responses:
- Input boxes should be uniform
- Labels should be uniform
### Project Goal:
### Project Goal
Make a Metahuman custom plugin for Maya, language: Python-based, Maya version: 2022, 2023, 2024, 2025
### Project Description:
### Project Description
- This project is a Maya plugin, the main function is to provide a model with the same topology as MetaHuman or a custom 3D model to complete custom binding, edit DNA, calibrate bone position, save DNA, load DNA, export fbx, save DNA file, edit BlendShape, and other functions.
### Project Functions:
### Project Functions
- Provide a model with the same topology as MetaHuman or a custom 3D model to complete custom binding, edit DNA, calibrate bone position, save DNA, load DNA, export fbx, save DNA file, edit BlendShape, and other functions.
### Important Variables
- Config File: `config.py`
- Main File: `scripts\Main.py`
- Ot Module: `scripts\ui\Qt.py`
- Style File: `scripts\ui\style.qss`
- Utilities Path: `scripts\utils\`
- Reload Module `scripts\ReloadModules.py`
- UI Modules: `scripts\ui\`
- UI 通用功能模块: `scripts\ui\ui_utils.py`
- 几何体UI模块 `scripts\ui\geometry.py`
- 几何体功能模块: `scripts\utils\utils_geometry.py`
- 绑定UI模块 `scripts\ui\rigging.py`
- 绑定功能模块: `scripts\utils\utils_rigging.py`
- 行为面板UI模块 `scripts\ui\behavior.py`
- 行为面板功能模块: `scripts\utils\utils_behavior.py`
- 定义面板UI模块 `scripts\ui\definition.py`
- 定义面板功能模块: `scripts\utils\utils_definition.py`
### Code structure
MetaHumanDNAPlugin/
├── Install.py # 安装脚本
├── config.py # 配置文件
├── Readme.md # 项目说明文档
├── Plan.md # 开发计划文档
├── scripts/ # 脚本目录
│ ├── Main.py # 主入口文件
│ ├── ReloadModules.py # 模块重载
│ │
│ ├── ui/ # UI模块目录
│ │ ├── Qt.py # Qt兼容模块
│ │ ├── style.qss # 统一样式文件
│ │ ├── ui_utils.py # UI通用功能
│ │ ├── geometry.py # 几何体UI
│ │ ├── rigging.py # 绑定UI
│ │ ├── behavior.py # 行为面板UI
│ │ └── definition.py # 定义面板UI
│ │
│ ├── utils/ # 工具函数目录
│ │ ├── utils_geometry.py # 几何体功能模块
│ │ ├── utils_rigging.py # 绑定功能模块
│ │ ├── utils_behaviour.py # 行为面板功能模块
│ │ └── utils_definition.py # 定义面板功能模块
│ │
│ ├── dnalib/ # DNA文件处理核心库
│ │ ├── __init__.py
│ │ ├── dna_reader.py # DNA读取器
│ │ ├── dna_writer.py # DNA写入器
│ │ └── dna_calibrator.py # DNA校准器
│ │
│ └── builder/ # 构建系统
│ ├── __init__.py
│ ├── skeleton_builder.py # 骨骼构建
│ ├── control_builder.py # 控制器构建
│ └── rig_builder.py # 绑定构建
├── Reference/ # 参考资料目录
├── DNA_Calibration/ # Epic官方DNA校准工具参考
├── MSLiveLink/ # MetaHuman LiveLink参考
├── SuperRigging/ # SuperRigging插件参考
│ ├── files/ # SuperRigging文件
│ ├── meta_anim/ # 动画相关参考
│ ├── meta_body_ctrl/ # 身体控制器参考
│ └── meta_motion_apply/ # 动作应用参考
├── SuperRiggingEditor/ # SuperRigging编辑器参考
├── Utils/ # 工具函数参考
└── UI/ # UI设计参考图
### TARGET FUNCTIONS
#### 1.1.1. Minimalist binding process
- One-button installation, one-button standardized model, one-button binding, one-button output FBX, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/05/cut_1.mp4, use context7;
- Through our project experience and technical accumulation, we can shorten the technical process to the maximum, without complicated configuration process, and the technology is automatic and one-click processing, so that your energy can focus more on role modeling, expression and performance.
#### 1.1.2. Quickly adjust the expression
- The expression range is adjusted in batches, and the expression is quickly mirrored, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E5%BF%AB%E9%80%9F%E8%B0%83%E6%95%B4.mp4, use context7;
- If the volume of some parts of your character (eyes/mouth/chin) is different from that of the standard Metahuman, you need to adjust the expression range of this area in batches to adapt to the correct expression, and you may need to adjust it one by one in the traditional process.
Now you can select these expressions in batches, and adjust the range with one button to quickly mirror the expressions, which will help you achieve the required facial expression quality faster and better.
#### 1.1.3. External expression import
- Support other expressions made by DCC to be imported into this role, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E5%A4%96%E9%83%A8%E8%A1%A8%E6%83%85%E5%AF%BC%E5%85%A5.mp4, use context7;
- In the actual production process, expressions need to be carved with Zbrush or other software, or many people collaborate. We support exporting and carving some expressions in the form of model files, and then importing them into the role after carving is completed, which solves the problem of collaboration in the expression production process, and all this can be achieved without you studying the underlying expression architecture of Metahuman, just by exporting and importing with one key.
#### 1.1.4. Create a template preset
- One-click creation for the current role is a template preset, and similar roles can be quickly bound without repeated work! use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E5%88%9B%E5%BB%BA%E6%A8%A1%E6%9D%BF%E9%A2%84%E8%AE%BE.mp4, use context7;
- In the production process, we will encounter a batch of characters with similar faces, but if each one needs to be rebound and repaired, the workload of repetition is huge, so we support converting the current character into a template with one click, and similar characters can be directly and perfectly bound with one click.
Can also be used for your binding file needs to fine-tune the face, change the body posture and other needs, first create the role as a template, and then re-bind, saving work.
#### 1.1.5. Automatic role correction
- Automatic correction of joint axis-&Pose of role, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E8%87%AA%E5%8A%A8%E6%A0%A1%E6%AD%A3%E8%A7%92%E8%89%B2.mp4, use context7;
- The role pose has different effects on the action capture and body modification of Metahuman. According to our experience, the role needs to fully conform to the pose of Metahuman, which can achieve higher data quality of dynamic capture and the correctness of finger axis.
We support one-click to correct the pose of the character to the correct pose, and set the axial direction of the joint correctly.
#### 1.1.6. Facial controller
- Face and panel controllers can be seamlessly switched, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E9%9D%A2%E9%83%A8%E6%8E%A7%E5%88%B6%E5%99%A8.mp4, use context7;
- Aiming at the problem that Metahuman's face control panel is not intuitive, we support the generation of a brand-new face controller, which can directly adjust the expression and muscles of the character on the face. If you contact Metahuman controller soon, it will greatly reduce your learning cost.
#### 1.1.7. Different topological binding
- Arbitrary model, without changing topology and UV, one-click binding, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E4%BB%BB%E6%84%8F%E6%8B%93%E6%89%91%E4%B8%80%E9%94%AE%E7%BB%91%E5%AE%9A.mp4, use context7;
- In the asset production process of most companies, the model is a set of fixed topological structure. If you want to bind it into Metahuman, the traditional process needs to be wrapped, and changing the topological structure also needs to change UV and Wrap. This link is easy to cause asset migration problems (model deformation, inaccurate topology, unable to Wrap the mapping format, UV layout does not meet asset characteristics and many other problems).
#### 1.1.8. Adapting UE specification
- The animation assets of UE, Animator and hair & clothing can be used seamlessly, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E5%AE%8C%E7%BE%8E%E9%80%82%E9%85%8DUE%E8%A7%84%E8%8C%83.mp4, use context7;
- Using the official code as the underlying framework, it is perfectly compatible with UE, and can seamlessly use all kinds of assets created by UE for Metahuman, so that your role can perform easily or perfectly.
## Reference
### Code reference (do not modify)
├── Reference/ # 参考资料目录 (不要修改)
├── DNA_Calibration/ # Epic官方DNA校准工具参考
├── MSLiveLink/ # MetaHuman LiveLink参考
├── SuperRigging/ # SuperRigging插件参考
│ ├── files/ # SuperRigging文件
│ ├── meta_anim/ # 动画相关参考
│ ├── meta_body_ctrl/ # 身体控制器参考
│ └── meta_motion_apply/ # 动作应用参考
├── SuperRiggingEditor/ # SuperRigging编辑器参考
├── Utils/ # 工具函数参考
└── UI/ # UI设计参考图
### Doc reference Do not modify
- [DNA_Calibration在线文档](https://epicgames.github.io/MetaHuman-DNA-Calibration), use context7;
- [MetaHuman DNA Calibration White Paper](https://cdn2.unrealengine.com/rig-logic-whitepaper-v2-zhcn-5860d80f8357.pdf), use context7;
- [MetaHuman DNA Calibration Code](https://github.com/EpicGames/MetaHuman-DNA-Calibration), use context7;
@@ -100,48 +213,3 @@ Make a Metahuman custom plugin for Maya, language: Python-based, Maya version: 2
- [MetaHuman Principle](https://zhuanlan.zhihu.com/p/673471863), use context7;
- [Metahuman 蒙皮、骨骼、驱动](https://blog.csdn.net/qq_28976599/article/details/130849821), use context7;
- [MetaHuman DNA Calibration Deep Dive](https://dev.epicgames.com/community/learning/tutorials/EoPj/metahuman-dna-calibration-deep-dive), use context7;
### 1.1. SuperRigging功能
#### 1.1.1. Minimalist binding process
- One-button installation, one-button standardized model, one-button binding, one-button output FBX, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/05/cut_1.mp4, use context7;
- Through our project experience and technical accumulation, we can shorten the technical process to the maximum, without complicated configuration process, and the technology is automatic and one-click processing, so that your energy can focus more on role modeling, expression and performance.
#### 1.1.2. Quickly adjust the expression
- The expression range is adjusted in batches, and the expression is quickly mirrored, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E5%BF%AB%E9%80%9F%E8%B0%83%E6%95%B4.mp4, use context7;
- If the volume of some parts of your character (eyes/mouth/chin) is different from that of the standard Metahuman, you need to adjust the expression range of this area in batches to adapt to the correct expression, and you may need to adjust it one by one in the traditional process.
Now you can select these expressions in batches, and adjust the range with one button to quickly mirror the expressions, which will help you achieve the required facial expression quality faster and better.
#### 1.1.3. External expression import
- Support other expressions made by DCC to be imported into this role, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E5%A4%96%E9%83%A8%E8%A1%A8%E6%83%85%E5%AF%BC%E5%85%A5.mp4, use context7;
- In the actual production process, expressions need to be carved with Zbrush or other software, or many people collaborate. We support exporting and carving some expressions in the form of model files, and then importing them into the role after carving is completed, which solves the problem of collaboration in the expression production process, and all this can be achieved without you studying the underlying expression architecture of Metahuman, just by exporting and importing with one key.
#### 1.1.4. Create a template preset
- One-click creation for the current role is a template preset, and similar roles can be quickly bound without repeated work! use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E5%88%9B%E5%BB%BA%E6%A8%A1%E6%9D%BF%E9%A2%84%E8%AE%BE.mp4, use context7;
- In the production process, we will encounter a batch of characters with similar faces, but if each one needs to be rebound and repaired, the workload of repetition is huge, so we support converting the current character into a template with one click, and similar characters can be directly and perfectly bound with one click.
Can also be used for your binding file needs to fine-tune the face, change the body posture and other needs, first create the role as a template, and then re-bind, saving work.
#### 1.1.5. Automatic role correction
- Automatic correction of joint axis-&Pose of role, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E8%87%AA%E5%8A%A8%E6%A0%A1%E6%AD%A3%E8%A7%92%E8%89%B2.mp4, use context7;
- The role pose has different effects on the action capture and body modification of Metahuman. According to our experience, the role needs to fully conform to the pose of Metahuman, which can achieve higher data quality of dynamic capture and the correctness of finger axis.
We support one-click to correct the pose of the character to the correct pose, and set the axial direction of the joint correctly.
#### 1.1.6. Facial controller
- Face and panel controllers can be seamlessly switched, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E9%9D%A2%E9%83%A8%E6%8E%A7%E5%88%B6%E5%99%A8.mp4, use context7;
- Aiming at the problem that Metahuman's face control panel is not intuitive, we support the generation of a brand-new face controller, which can directly adjust the expression and muscles of the character on the face. If you contact Metahuman controller soon, it will greatly reduce your learning cost.
#### 1.1.7. Different topological binding
- Arbitrary model, without changing topology and UV, one-click binding, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E4%BB%BB%E6%84%8F%E6%8B%93%E6%89%91%E4%B8%80%E9%94%AE%E7%BB%91%E5%AE%9A.mp4, use context7;
- In the asset production process of most companies, the model is a set of fixed topological structure. If you want to bind it into Metahuman, the traditional process needs to be wrapped, and changing the topological structure also needs to change UV and Wrap. This link is easy to cause asset migration problems (model deformation, inaccurate topology, unable to Wrap the mapping format, UV layout does not meet asset characteristics and many other problems).
#### 1.1.8. Adapting UE specification
- The animation assets of UE, Animator and hair & clothing can be used seamlessly, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E5%AE%8C%E7%BE%8E%E9%80%82%E9%85%8DUE%E8%A7%84%E8%8C%83.mp4, use context7;
- Using the official code as the underlying framework, it is perfectly compatible with UE, and can seamlessly use all kinds of assets created by UE for Metahuman, so that your role can perform easily or perfectly.
### Code reference (do not modify)
- `Reference\DNA_Calibration`
- `Reference\MSLiveLink`
- `Reference\SuperRiggingEditor`
- `Reference\Utils`
### UI reference images:
- `Reference\UI`

161
Plan.md
View File

@@ -1,41 +1,69 @@
# Maya插件开发计划
## 1. 项目目标与功能梳理
- 支持MetaHuman同拓扑或自定义3D模型的绑定、DNA编辑、骨骼校准、DNA保存/加载、FBX导出、BlendShape编辑等。
- 兼容Maya 2022-2025Python 3.10-3.12UTF-8编码。
- UI采用Qt参考`scripts/Qt.py`),样式统一在`scripts/ui/style.qss`
- 参考Epic官方MetaHuman DNA Calibration工具及相关文档吸收SuperRigging等插件的设计思路。
- 实现一键式工作流程,简化技术流程,让用户专注于角色建模、表情和表演。
## 2. 主要模块划分
1. **配置与主控**
- `config.py`(配置文件)
- `scripts/Main.py`(主入口)
- `scripts/ReloadModules.py`(模块热重载)
2. **UI层**
- `scripts/ui/`所有UI模块含主界面、子工具面板
- `scripts/ui/Qt.py`Qt封装
- `scripts/ui/style.qss`(统一样式)
- `scripts/ui/ui_utils.py`UI通用功能模块
- `scripts/ui/geometry.py`几何体UI模块
- `scripts/ui/rigging.py`绑定UI模块
- `scripts/ui/behaviour.py`行为面板UI模块
- `scripts/ui/definition.py`(定义面板UI模块
3. **功能实现**
- `scripts/utils/utils_geometry.py`(几何体功能模块)
- `scripts/utils/utils_rigging.py`(绑定功能模块)
- `scripts/utils/utils_behaviour.py`(行为面板功能模块)
- `scripts/utils/utils_definition.py`(定义面板功能模块)
- DNA文件处理加载/保存/编辑)
- 绑定与骨骼校准
- BlendShape与控制器映射
- FBX导出
- 兼容MetaHuman DNA标准
4. **DNA处理核心**
- `scripts/dnalib/`DNA文件处理核心库
- `scripts/builder/`构建系统负责从DNA文件创建Maya场景
MetaHumanDNAPlugin/
├── Install.py # 安装脚本
├── config.py # 配置文件
├── Readme.md # 项目说明文档
├── Plan.md # 开发计划文档
├── scripts/ # 脚本目录
├── Main.py # 主入口文件
├── ReloadModules.py # 模块重载
├── ui/ # UI模块目录
│ │ ├── Qt.py # Qt兼容模块
│ ├── style.qss # 统一样式文件
│ ├── ui_utils.py # UI通用功能
│ ├── geometry.py # 几何体UI
│ │ ├── rigging.py # 绑定UI
│ ├── behavior.py # 行为面板UI
│ └── definition.py # 定义面板UI
├── utils/ # 工具函数目录
│ ├── utils_geometry.py # 几何体功能模块
│ │ ├── utils_rigging.py # 绑定功能模块
│ ├── utils_behaviour.py # 行为面板功能模块
│ └── utils_definition.py # 定义面板功能模块
│ │
│ ├── dnalib/ # DNA文件处理核心库
│ │ ├── __init__.py
│ │ ├── dna_reader.py # DNA读取器
│ │ ├── dna_writer.py # DNA写入器
│ │ └── dna_calibrator.py # DNA校准器
│ │
│ └── builder/ # 构建系统
│ ├── __init__.py
│ ├── skeleton_builder.py # 骨骼构建
│ ├── control_builder.py # 控制器构建
│ └── rig_builder.py # 绑定构建
├── Reference/ # 参考资料目录
├── DNA_Calibration/ # Epic官方DNA校准工具参考
├── MSLiveLink/ # MetaHuman LiveLink参考
├── SuperRigging/ # SuperRigging插件参考
│ ├── files/ # SuperRigging文件
│ ├── meta_anim/ # 动画相关参考
│ ├── meta_body_ctrl/ # 身体控制器参考
│ └── meta_motion_apply/ # 动作应用参考
├── SuperRiggingEditor/ # SuperRigging编辑器参考
├── Utils/ # 工具函数参考
└── UI/ # UI设计参考图
## 3. 阶段性开发计划
### 第一阶段基础框架与UI
- [x] 搭建插件基础结构,主入口、配置加载
- [x] 主界面UI搭建参考UI图与.qss风格
- [x] 完善主界面各功能区分栏(模型、绑定、调整、定义)
@@ -52,6 +80,7 @@
- [ ] 优化UI性能减少重绘和刷新操作
### 第二阶段:核心功能开发
- [ ] DNA文件读写与MetaHuman兼容
- [ ] 完善dnalib模块实现DNA文件的读取与解析
- [ ] 实现DNA文件的保存与导出
@@ -60,35 +89,73 @@
- [ ] 实现MetaHuman同拓扑模型的自动绑定
- [ ] 支持自定义模型的绑定流程
- [ ] 实现骨骼映射与权重传递
- [ ] 实现一键式标准化模型、一键绑定、一键输出FBX功能
- [ ] 骨骼校准与自动分组
- [ ] 实现骨骼自动分组算法
- [ ] 支持骨骼位置、旋转的微调与校准
- [ ] 实现关节轴向自动校正
- [ ] 实现角色姿态自动校正功能
- [ ] BlendShape与控制器映射
- [ ] 实现BlendShape的创建、编辑与管理
- [ ] 支持控制器与BlendShape的映射关系设置
- [ ] 实现表情范围调整与镜像功能
- [ ] 支持批量调整表情范围和快速镜像表情
- [ ] FBX导出与DNA文件导出
- [ ] 实现模型与骨骼的FBX导出
- [ ] 支持DNA文件的导出与版本控制
- [ ] 确保导出文件与MetaHuman兼容
### 第三阶段:高级功能与优化
- [ ] 批量处理与自动化工具
- [ ] 开发批量处理模型的工具
- [ ] 实现自动化绑定流程
- [ ] 支持批量表情编辑与导出
- [ ] 多LOD支持与切换
- [ ] 实现多LOD模型的自动生成
- [ ] 支持LOD间的权重与表情传递
- [ ] 开发LOD切换与预览功能
- [ ] 配置与模板管理
- [ ] 设计配置保存与加载系统
- [ ] 实现模板创建与应用功能
- [ ] 支持配置的导入与导出
- [ ] 实现一键创建模板预设功能,支持相似角色快速绑定
- [ ] 插件性能与兼容性优化
- [ ] 优化大型模型的处理效率
- [ ] 提高DNA文件处理速度
- [ ] 确保在不同Maya版本中的兼容性
- [ ] 实现多线程处理以提升性能
- [ ] 面部控制器系统
- [ ] 开发新的面部控制器系统
- [ ] 实现面部控制器与面板控制器的无缝切换
- [ ] 支持直接在面部上调整表情和肌肉
- [ ] 外部表情导入系统
- [ ] 支持从其他DCC软件导入表情
- [ ] 实现表情模型文件的导出与导入
- [ ] 支持协作式表情制作流程
### 第四阶段:文档与测试
- [ ] 全流程测试脚本
- [ ] 开发自动化测试脚本
- [ ] 测试不同拓扑结构的模型
- [ ] 验证在不同Maya版本中的兼容性
- [ ] 测试与UE资产的兼容性
- [ ] 用户操作手册与开发文档完善
- [ ] 编写详细的用户手册
- [ ] 完善开发文档与API说明
- [ ] 制作教学视频与示例
- [ ] 参考文档和代码持续同步
- [ ] 确保文档与代码的一致性
- [ ] 更新最新的技术说明
- [ ] 添加常见问题解答
---
## 进度记录与后续安排
### 2025-05-06 更新
- 基础框架与UI阶段第一阶段已完成95%
- 完成了主要UI模块的创建和基本功能实现
- 优化了项目结构,明确了各模块的职责和接口
@@ -96,6 +163,7 @@
- DNA处理核心模块已初步实现需要进一步完善与测试
### 2025-04-30 更新
- 完成了基础框架与UI阶段的主要任务第一阶段 90%
- 优化了主窗口与各功能面板的UI结构确保风格统一
- 创建了所有必要的面板类,包括:
@@ -105,16 +173,39 @@
- 定义面板UI模块definition.py
### 下一步开发计划
1. **完成UI微调与优化**
- 统一按钮样式与图标
- 优化面板布局与响应式设计
- 完善多语言支持
- 增强UI与Maya交互的稳定性
2. **开始DNA文件读写与MetaHuman兼容功能开发**
- 完善dnalib模块实现DNA文件的读取与解析
- 实现DNA文件的保存与导出
- 确保与MetaHuman DNA标准兼容
### 1. 完成UI微调与优化
- 统一按钮样式与图标
- 优化面板布局与响应式设计
- 完善多语言支持
- 增强UI与Maya交互的稳定性
### 2. 开始DNA文件读写与MetaHuman兼容功能开发
- 完善dnalib模块实现DNA文件的读取与解析
- 实现DNA文件的保存与导出
- 确保与MetaHuman DNA标准兼容
### 3. 实现一键式工作流程
- 开发一键安装功能
- 实现一键标准化模型功能
- 开发一键绑定功能
- 实现一键输出FBX功能
### 4. 开发表情调整功能
- 实现批量调整表情范围
- 开发表情快速镜像功能
- 支持外部表情导入
### 5. 开发多LOD支持与切换功能
- 实现多LOD模型的自动生成
- 支持LOD间的权重与表情传递
- 开发LOD切换与预览功能
- 每完成一个功能模块,及时更新开发计划文档与进度百分比。
- 代码变更后,自动检查代码规范、插件完整性,并给出下一步开发建议。

154
Readme.md
View File

@@ -1,126 +1,58 @@
# MetaHuman DNA 自定义绑定插件
## 项目介绍
- Maya的Metahuman自定义绑定插件
- 语言: 基于Python
- Maya版本: 2022, 2023, 2024, 2025
- 本项目是一个Maya插件提供全面的MetaHuman自定义解决方案包括DNA编辑、骨骼校准、BlendShape编辑、动画交换等功能
## Project Introduction
## 核心功能
### DNA浏览器
- 自动读取DNA文件显示在项目浏览器中
- 可以打开、保存DNA文件
- 可以加载DNA文件以供编辑
- 可编辑DNA文件基本信息
- DNA文件保存和写入
### 模型编辑DNA编辑与校准
- 自定义造型模型快速指认和绑定Metahuman拓扑
- 自动生成其他身体的配件(睫毛,泪腺,舌头等)
- 校准骨骼位置以匹配自定义模型
- 自动修复接缝和权重问题
- 根据DNA生成身体并支持自定义绑定
- 头和身体接缝修复
- 支持自定义拓扑结构的模型绑定
- 赋予材质,以及顶点色
- FBX模型导入和导出
### 面部表情系统
- 编辑和优化BlendShape
- 批量导出和导入BlendShape支持表情克隆
- 支持Blendshape修改并更新DNA
- 表情范围调整和克隆
### 动画系统
- 坐标系统自动转换
- 支持面部和身体动画
- RBF变形器用于不同体型间的衣物配饰转移
- 支持动画导入和导出
- 动画实时预览系统
- Maya's Metahuman custom binding plugin
- Language: Based on Python
- Maya version: 2022, 2023, 2024, 2025
- This project is a Maya plugin that provides a comprehensive MetaHuman custom solution, including DNA editing, bone calibration, BlendShape editing, animation exchange, etc.
## 主要模块
## Features
### 工具栏 Toolbar
- 加载预设
- 保存预设
- 导入DNA
- 导出DNA
- 创建RL4节点用于切换DNA编辑的状态
- 删除RL4节点用于切换DNA编辑的状态
#### 1.1.1. Minimalist binding process
### 几何体 Geomery
- 模型拾取以及加载
- LOD模型分级过滤
- LOD模型创建
- 自动加载模型
- 标准化命名
- 自动分组
- 生成面部配件(睫毛,舌头,泪腺 等)
- 修复接缝(修复法线)
- 修复点序
- One-button installation, one-button standardized model, one-button binding, one-button output FBX, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/05/cut_1.mp4, use context7;
- Through our project experience and technical accumulation, we can shorten the technical process to the maximum, without complicated configuration process, and the technology is automatic and one-click processing, so that your energy can focus more on role modeling, expression and performance.
### 绑定 Rigging
- DNA浏览器
- 根据DNA导入骨骼
- 根据DNA生成身体
- DNA校准
- 骨骼位置校准
- 创建绑定
- 复制蒙皮
#### 1.1.2. Quickly adjust the expression
### 行为 Behaviour
- Blendshape自动加载刷新筛选
- 次级Blendshape自动加载刷新筛选
- Blendshape批量导出和导入
- Blendshape范围编辑
- Blendshape镜像
- Blendshape查找翻转目标
- Blendshape重建
- 表情控制器还原默认表情
- 查找选择表情
- 控制面板查找
- 选择关联关节
- 写入当前表情
- 写入镜像表情
- The expression range is adjusted in batches, and the expression is quickly mirrored, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E5%BF%AB%E9%80%9F%E8%B0%83%E6%95%B4.mp4, use context7;
- If the volume of some parts of your character (eyes/mouth/chin) is different from that of the standard Metahuman, you need to adjust the expression range of this area in batches to adapt to the correct expression, and you may need to adjust it one by one in the traditional process.
Now you can select these expressions in batches, and adjust the range with one button to quickly mirror the expressions, which will help you achieve the required facial expression quality faster and better.
#### 1.1.3. External expression import
- Support other expressions made by DCC to be imported into this role, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E5%A4%96%E9%83%A8%E8%A1%A8%E6%83%85%E5%AF%BC%E5%85%A5.mp4, use context7;
- In the actual production process, expressions need to be carved with Zbrush or other software, or many people collaborate. We support exporting and carving some expressions in the form of model files, and then importing them into the role after carving is completed, which solves the problem of collaboration in the expression production process, and all this can be achieved without you studying the underlying expression architecture of Metahuman, just by exporting and importing with one key.
### 定义 Definition
- LOD, Meshes, Joints, Blendshape, AnimatedMap 加载和刷新
- 写入: 写入关节默认位置,写入几何体,写入蒙皮,写入混合变形目标
- 创建:创建混合变形,绑定蒙皮,取消蒙皮
- 工具:重新定位头部关节,重新定位身体关节,重新定位全身关节,快速创建预设
#### 1.1.4. Create a template preset
- One-click creation for the current role is a template preset, and similar roles can be quickly bound without repeated work! use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E5%88%9B%E5%BB%BA%E6%A8%A1%E6%9D%BF%E9%A2%84%E8%AE%BE.mp4, use context7;
- In the production process, we will encounter a batch of characters with similar faces, but if each one needs to be rebound and repaired, the workload of repetition is huge, so we support converting the current character into a template with one click, and similar characters can be directly and perfectly bound with one click.
Can also be used for your binding file needs to fine-tune the face, change the body posture and other needs, first create the role as a template, and then re-bind, saving work.
## 技术特点
- 基于Epic Games的MetaHuman-DNA-Calibration库
- 集成PoseWrangler系统实现RBF变形
- 优化算法提升性能
- 多语言界面支持(中文简体、繁体、英语、日语、韩语、法语)
#### 1.1.5. Automatic role correction
## 开发目标
1. 实现完整的DNA校准和编辑功能
2. 提供直观的用户界面,简化复杂操作
3. 支持多种拓扑结构的自定义模型
4. 优化性能,提高大型模型的处理效率
5. 实现UE与Maya之间的无缝动画交换
6. 提供全面的文档和教程
- Automatic correction of joint axis-&Pose of role, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E8%87%AA%E5%8A%A8%E6%A0%A1%E6%AD%A3%E8%A7%92%E8%89%B2.mp4, use context7;
- The role pose has different effects on the action capture and body modification of Metahuman. According to our experience, the role needs to fully conform to the pose of Metahuman, which can achieve higher data quality of dynamic capture and the correctness of finger axis.
We support one-click to correct the pose of the character to the correct pose, and set the axial direction of the joint correctly.
## 开发路线图
- [x] 基础DNA读取和修改功能
- [x] 骨骼校准系统
- [x] BlendShape编辑工具
- [x] 面部表情系统
- [x] 动画导入导出
- [x] 多拓扑支持
- [x] RBF变形器
- [x] 一键创建LOD
- [x] 自动修复工具
- [x] 赋予材质,以及顶点色
- [x] 动画实时预览系统
#### 1.1.6. Facial controller
## 安装与使用
1. 将插件文件复制到Maya插件目录
2. 在Maya中加载插件
3. 使用插件界面进行操作
- Face and panel controllers can be seamlessly switched, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E9%9D%A2%E9%83%A8%E6%8E%A7%E5%88%B6%E5%99%A8.mp4, use context7;
- Aiming at the problem that Metahuman's face control panel is not intuitive, we support the generation of a brand-new face controller, which can directly adjust the expression and muscles of the character on the face. If you contact Metahuman controller soon, it will greatly reduce your learning cost.
#### 1.1.7. Different topological binding
- Arbitrary model, without changing topology and UV, one-click binding, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E4%BB%BB%E6%84%8F%E6%8B%93%E6%89%91%E4%B8%80%E9%94%AE%E7%BB%91%E5%AE%9A.mp4, use context7;
- In the asset production process of most companies, the model is a set of fixed topological structure. If you want to bind it into Metahuman, the traditional process needs to be wrapped, and changing the topological structure also needs to change UV and Wrap. This link is easy to cause asset migration problems (model deformation, inaccurate topology, unable to Wrap the mapping format, UV layout does not meet asset characteristics and many other problems).
#### 1.1.8. Adapting UE specification
- The animation assets of UE, Animator and hair & clothing can be used seamlessly, use context7;
- https://pointart.oss-cn-hangzhou.aliyuncs.com/wp-content/uploads/2024/06/%E5%AE%8C%E7%BE%8E%E9%80%82%E9%85%8DUE%E8%A7%84%E8%8C%83.mp4, use context7;
- Using the official code as the underlying framework, it is perfectly compatible with UE, and can seamlessly use all kinds of assets created by UE for Metahuman, so that your role can perform easily or perfectly.
## 依赖项
- Maya 2022或更高版本
- Python 3.7+
- MetaHuman-DNA-Calibration库
- PoseWrangler系统

View File

@@ -14,7 +14,7 @@ TOOL_YEAR = "2025"
TOOL_MOD_FILENAME = f"{TOOL_NAME.lower()}.mod"
TOOL_LANG = "en_US"
TOOL_WSCL_NAME = f"{TOOL_NAME}WorkspaceControl"
TOOL_HELP_URL = f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki"
TOOL_HELP_URL = f"http://10.72.61.59:3000/ArtGroup/{TOOL_NAME}/wiki"
# Path Configuration
TOOL_PATH = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
@@ -31,7 +31,7 @@ TOOL_COMMAND_ICON = TOOL_ICON
# UI 默认尺寸
TOOL_HEIGHT = 800
TOOL_WIDTH = 600
TOOL_WIDTH = 500
# DNA Config
# GUI相关常量

View File

@@ -32,6 +32,7 @@ from scripts.ui import definition
#========================================= LOCALIZATION =====================================
from scripts.ui import localization
LANG = localization.LANG
get_text = localization.get_text
#=========================================== CONFIG =========================================
import config
TOOL_NAME = config.TOOL_NAME
@@ -68,9 +69,6 @@ class MainWindow(QtWidgets.QWidget):
# 设置窗口为独立窗口
self.setWindowFlags(QtCore.Qt.Window)
# 设置窗口位置为屏幕中央
self.center_window()
# 设置窗口图标
if os.path.exists(TOOL_ICON):
self.setWindowIcon(QtGui.QIcon(TOOL_ICON))
@@ -109,14 +107,18 @@ class MainWindow(QtWidgets.QWidget):
# 创建工具栏区域
self.toolbar_widget = QtWidgets.QWidget()
self.toolbar_widget.setObjectName("toolbarWidget")
self.toolbar_widget.setMaximumHeight(80) # 限制工具栏高度
self.toolbar_widget.setMaximumHeight(80)
# 创建功能区域切换按钮组
self.function_buttons = {}
self.function_buttons["geometry"] = QtWidgets.QPushButton(LANG.get("geometry", "几何体"))
self.function_buttons["rigging"] = QtWidgets.QPushButton(LANG.get("rigging", "绑定"))
self.function_buttons["behaviour"] = QtWidgets.QPushButton(LANG.get("behaviour", "行为"))
self.function_buttons["definition"] = QtWidgets.QPushButton(LANG.get("definition", "定义"))
self.function_buttons["geometry"] = QtWidgets.QPushButton(get_text("geometry", "几何体"))
self.function_buttons["geometry"].setIcon(ui_utils.load_icon("meshes.png"))
self.function_buttons["rigging"] = QtWidgets.QPushButton(get_text("rigging", "绑定"))
self.function_buttons["rigging"].setIcon(ui_utils.load_icon("configuration.png"))
self.function_buttons["behaviour"] = QtWidgets.QPushButton(get_text("behaviour", "行为"))
self.function_buttons["behaviour"].setIcon(ui_utils.load_icon("behaviour.png"))
self.function_buttons["definition"] = QtWidgets.QPushButton(get_text("definition", "定义"))
self.function_buttons["definition"].setIcon(ui_utils.load_icon("definition.png"))
# 设置按钮样式和属性
for key, button in self.function_buttons.items():
@@ -224,64 +226,18 @@ class MainWindow(QtWidgets.QWidget):
for i, (key, button) in enumerate(self.function_buttons.items()):
button.setChecked(i == index)
# 调整分割器宽度
self.reset_splitters()
# 打印当前面板信息
panel_names = ["几何体", "绑定", "行为", "定义"]
print(f"切换到功能面板: {panel_names[index]}")
def reset_splitters(self):
"""重置所有分割器的宽度,确保左右栏宽度均等"""
# 重置定义面板分割器
if hasattr(self.definition_ui, 'reset_splitter_sizes'):
self.definition_ui.reset_splitter_sizes()
# 重置行为面板分割器
if hasattr(self.behaviour_ui, 'reset_splitter_sizes'):
self.behaviour_ui.reset_splitter_sizes()
# 重置绑定面板分割器
if hasattr(self.rigging_ui, 'reset_splitter_sizes'):
self.rigging_ui.reset_splitter_sizes()
# 重置几何面板分割器
if hasattr(self.geometry_ui, 'reset_splitter_sizes'):
self.geometry_ui.reset_splitter_sizes()
def center_window(self):
"""将窗口居中显示在屏幕上"""
# 获取屏幕几何信息 - 兼容PySide2和PySide6
try:
# 使用Qt.py兼容层方式获取屏幕信息
app = QtWidgets.QApplication.instance()
screen = app.primaryScreen() if hasattr(app, 'primaryScreen') else None
if screen:
screen_rect = screen.availableGeometry()
else:
# 尝试使用desktop方法PySide2兼容
if hasattr(QtWidgets.QApplication, 'desktop'):
desktop = QtWidgets.QApplication.desktop()
screen_rect = desktop.availableGeometry(desktop.primaryScreen())
else:
# 如果都失败,使用默认值
screen_rect = QtCore.QRect(0, 0, 1920, 1080)
except Exception as e:
print(f"获取屏幕信息时出错: {str(e)}")
# 使用默认值
screen_rect = QtCore.QRect(0, 0, 1920, 1080)
# 计算窗口居中位置
window_rect = self.frameGeometry()
center_point = screen_rect.center()
window_rect.moveCenter(center_point)
# 移动窗口到居中位置
self.move(window_rect.topLeft())
def main():
"""主函数,创建并显示主窗口"""
try:
# 应用当前语言设置
from scripts.ui import localization
config.TOOL_LANG = localization.get_current_language()
# 如果已存在窗口,则关闭
for widget in QtWidgets.QApplication.allWidgets():
if widget.objectName() == f"{TOOL_NAME}MainWindow" and isinstance(widget, QtWidgets.QWidget):

View File

@@ -411,7 +411,7 @@ class ModuleReloaderUI(QtWidgets.QDialog):
parent = parent or getMayaMainWindow()
super(ModuleReloaderUI, self).__init__(parent)
self.setWindowTitle("Delos - 模块重载工具")
self.setWindowTitle(f"{TOOL_NAME} - 模块重载工具")
self.setMinimumWidth(600)
self.setMinimumHeight(500)
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
@@ -427,7 +427,7 @@ class ModuleReloaderUI(QtWidgets.QDialog):
main_layout.setSpacing(10)
# 标题标签
title_label = QtWidgets.QLabel("Delos 模块重载工具")
title_label = QtWidgets.QLabel(f"{TOOL_NAME} 模块重载工具")
title_label.setStyleSheet("font-size: 16px; font-weight: bold;")
main_layout.addWidget(title_label)
@@ -639,7 +639,7 @@ def show_reload_ui():
"""显示模块重载UI"""
try:
# 关闭已有的窗口 - 使用多种方法确保关闭
window_title = "Delos - 模块重载工具"
window_title = f"{TOOL_NAME} - 模块重载工具"
window_closed = False
# 方法1: 使用窗口标题查找

View File

@@ -58,6 +58,7 @@ TOOL_HEIGHT = config.TOOL_HEIGHT
#========================================= LOCATION =======================================
from scripts.ui import localization
LANG = localization.LANG
get_text = localization.get_text
class BehaviourUI(ui_utils.BaseUI):
"""
@@ -82,12 +83,14 @@ class BehaviourUI(ui_utils.BaseUI):
初始化行为系统UI
"""
super(BehaviourUI, self).__init__() # 不传递parent参数给BaseUI
self.main_widget = QtWidgets.QWidget(parent) # 在创建main_widget时传递parent
self.main_widget.setObjectName("behaviourMainWidget")
# 设置单例实例
BehaviourUI._instance = self
# 创建主控件
self.main_widget = QtWidgets.QWidget(parent)
self.main_widget.setObjectName("behaviourMainWidget")
# 初始化控件、布局和按钮字典
self.controls = {}
self.layouts = {}
@@ -99,18 +102,15 @@ class BehaviourUI(ui_utils.BaseUI):
self.create_layouts()
self.create_connections()
# 在创建完所有布局后,设置分割器的初始大小和属性
# 使用ui_utils中的函数来设置分割器大小
ui_utils.setup_splitter(self, "main_splitter", [1, 1])
# 设置分割器所有子元素的最小尺寸为0确保可以自由调整
ui_utils.set_splitter_children_minimum_size(self, recursive=True)
# 设置所有控件的最小尺寸为0确保分割器可以自由移动
ui_utils.set_all_controls_minimum_size(self)
# 使用ui_utils中的函数强制设置均等大小
ui_utils.force_equal_splitter_sizes(self)
# 设置全局样式,确保所有控件都没有最小宽度限制
self.main_widget.setStyleSheet("""
QWidget { min-width: 0px; }
QPushButton { min-width: 0px; }
QLabel { min-width: 0px; }
QListWidget { min-width: 0px; }
QGroupBox { min-width: 0px; }
QSlider { min-width: 0px; }
""")
def create_widgets(self):
"""
@@ -118,39 +118,35 @@ class BehaviourUI(ui_utils.BaseUI):
包括标题标签、搜索框、控制列表、滑块等
"""
# 标题标签
self.controls["title_label"] = QtWidgets.QLabel(LANG.get("behaviour_title", "行为系统"))
self.controls["title_label"] = QtWidgets.QLabel(get_text("behaviour_title", "行为系统"))
self.controls["title_label"].setObjectName("behaviourTitleLabel")
self.controls["title_label"].setAlignment(QtCore.Qt.AlignCenter)
self.controls["title_label"].setStyleSheet("font-size: 14px; font-weight: bold; padding: 5px;")
# 主分割器
# 主分割器 - 关键设置
self.splitters["main_splitter"] = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
self.splitters["main_splitter"].setObjectName("behaviourMainSplitter")
self.splitters["main_splitter"].setHandleWidth(6) # 设置分割器手柄宽度
self.splitters["main_splitter"].setChildrenCollapsible(False) # 禁止子部件折叠
self.splitters["main_splitter"].setChildrenCollapsible(True) # 允许子部件折叠 - 关键设置
self.splitters["main_splitter"].setOpaqueResize(True) # 实时显示调整效果,更丝滑
# 左侧面板 - Raw Control
self.controls["left_panel"] = QtWidgets.QWidget()
self.controls["left_panel"].setObjectName("behaviourLeftPanel")
self.controls["left_panel"].setMinimumWidth(0)
self.controls["left_panel"].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# 右侧面板 - BlendShapes
self.controls["right_panel"] = QtWidgets.QWidget()
self.controls["right_panel"].setObjectName("behaviourRightPanel")
self.controls["right_panel"].setMinimumWidth(0)
self.controls["right_panel"].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# 搜索框
self.controls["search_input"] = QtWidgets.QLineEdit()
self.controls["search_input"].setObjectName("searchInput")
self.controls["search_input"].setPlaceholderText(LANG.get("search", "搜索..."))
self.controls["search_input"].setPlaceholderText(get_text("search", "搜索..."))
self.controls["search_input"].setFixedHeight(25)
# 控制列表
self.controls["control_list"] = QtWidgets.QListWidget()
self.controls["control_list"].setObjectName("controlList")
self.controls["control_list"].setMinimumWidth(0) # 设置最小宽度为零
# 底部滑块
self.controls["raw_slider"] = QtWidgets.QSlider(QtCore.Qt.Horizontal)
@@ -160,35 +156,17 @@ class BehaviourUI(ui_utils.BaseUI):
self.controls["raw_slider"].setValue(0)
self.controls["raw_slider"].setFixedHeight(20)
# 创建滑块布局,包含数值显示和滑块
self.layouts["raw_slider_layout"] = QtWidgets.QHBoxLayout()
self.layouts["raw_slider_layout"].setContentsMargins(0, 0, 0, 0)
self.layouts["raw_slider_layout"].setSpacing(2)
# 添加数值显示标签
self.controls["raw_slider_value"] = QtWidgets.QLabel("0.000")
self.controls["raw_slider_value"].setObjectName("rawSliderValue")
self.controls["raw_slider_value"].setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.controls["raw_slider_value"].setFixedWidth(40)
self.layouts["raw_slider_layout"].addWidget(self.controls["raw_slider_value"])
# 添加滑块
self.layouts["raw_slider_layout"].addWidget(self.controls["raw_slider"])
# 添加"All"勾选框和标签
self.controls["raw_slider_all_check"] = QtWidgets.QCheckBox("All")
self.controls["raw_slider_all_check"].setObjectName("rawSliderAllCheck")
self.layouts["raw_slider_layout"].addWidget(self.controls["raw_slider_all_check"])
# 页码按钮
self.buttons["prev_page"] = QtWidgets.QPushButton("上一页")
self.buttons["prev_page"].setObjectName("prevPageButton")
self.buttons["prev_page"].setIcon(ui_utils.load_icon("arrowLeft.png"))
self.buttons["next_page"] = QtWidgets.QPushButton("下一页")
self.buttons["next_page"].setObjectName("nextPageButton")
self.buttons["next_page"].setIcon(ui_utils.load_icon("arrowRight.png"))
self.buttons["page_all"] = QtWidgets.QPushButton("All")
self.buttons["page_all"].setObjectName("pageAllButton")
self.buttons["page_all"].setCheckable(True)
@@ -215,260 +193,250 @@ class BehaviourUI(ui_utils.BaseUI):
self.buttons["page_6"].setCheckable(True)
# 左下角Range按钮
self.buttons["range_plus"] = QtWidgets.QPushButton("Range +")
self.buttons["range_plus"].setObjectName("rangePlusButton")
self.buttons["range_plus"].setIcon(ui_utils.load_icon("behaviour.png"))
self.buttons["range_minus"] = QtWidgets.QPushButton("Range -")
self.buttons["range_minus"] = QtWidgets.QPushButton(get_text(" Range - ", " 范围 - "))
self.buttons["range_minus"].setObjectName("rangeMinusButton")
self.buttons["range_minus"].setIcon(ui_utils.load_icon("behaviour.png"))
self.buttons["range_minus"].setMinimumWidth(0)
self.buttons["range_plus"] = QtWidgets.QPushButton(get_text(" Range + ", " 范围 + "))
self.buttons["range_plus"].setObjectName("rangePlusButton")
self.buttons["range_plus"].setIcon(ui_utils.load_icon("behaviour.png"))
self.buttons["range_plus"].setMinimumWidth(0)
# 左侧面板控件 - Raw Control
self.controls["raw_control_group"] = QtWidgets.QGroupBox(get_text("Raw Control", "原始控制"))
self.controls["raw_control_group"].setObjectName("rawControlGroup")
self.controls["raw_control_group"].setTitle(get_text("Raw Control", "原始控制"))
self.controls["raw_control_group"].setFixedHeight(25)
# 右侧面板控件 - Related BlendShapes
self.controls["blendshapes_group"] = QtWidgets.QGroupBox("Related BlendShapes [000]")
self.controls["blendshapes_group"] = QtWidgets.QGroupBox(get_text("Related BlendShapes", "相关BlendShapes"))
self.controls["blendshapes_group"].setObjectName("blendshapesGroup")
self.controls["blendshapes_group"].setMinimumWidth(0) # 设置最小宽度为零
self.controls["blendshapes_group"].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.controls["blendshapes_group"].setTitle(get_text("Related BlendShapes", "相关BlendShapes"))
self.controls["blendshapes_group"].setFixedHeight(25)
# BlendShapes列表
self.controls["blendshapes_list"] = QtWidgets.QListWidget()
self.controls["blendshapes_list"].setObjectName("blendshapesList")
self.controls["blendshapes_list"].setMinimumWidth(0) # 设置最小宽度为零
self.controls["blendshapes_list"].setMinimumHeight(100) # 设置最小高度,确保列表可见
self.controls["blendshapes_list"].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.controls["blendshapes_list"].setMinimumHeight(100)
# 底部滑块
# 底部BS滑块
self.controls["bs_slider"] = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.controls["bs_slider"].setObjectName("bsSlider")
self.controls["bs_slider"].setMinimum(0)
self.controls["bs_slider"].setMaximum(100)
self.controls["bs_slider"].setValue(0)
# 创建BS滑块布局包含数值显示和滑块
self.layouts["bs_slider_layout"] = QtWidgets.QHBoxLayout()
self.layouts["bs_slider_layout"].setContentsMargins(0, 0, 0, 0)
self.layouts["bs_slider_layout"].setSpacing(2)
# 添加数值显示标签
# 添加BS数值显示标签
self.controls["bs_slider_value"] = QtWidgets.QLabel("0.000")
self.controls["bs_slider_value"].setObjectName("bsSliderValue")
self.controls["bs_slider_value"].setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.controls["bs_slider_value"].setFixedWidth(40)
self.layouts["bs_slider_layout"].addWidget(self.controls["bs_slider_value"])
# 添加滑块
self.layouts["bs_slider_layout"].addWidget(self.controls["bs_slider"])
# 添加"All"勾选框和标签
# 添加BS "All"勾选框和标签
self.controls["bs_slider_all_check"] = QtWidgets.QCheckBox("All")
self.controls["bs_slider_all_check"].setObjectName("bsSliderAllCheck")
self.layouts["bs_slider_layout"].addWidget(self.controls["bs_slider_all_check"])
# BlendShape操作按钮
self.buttons["flip_target"] = QtWidgets.QPushButton("Flip Target")
self.buttons["flip_target"].setObjectName("flipTargetButton")
self.buttons["flip_target"].setIcon(ui_utils.load_icon("mirrorL.png"))
self.buttons["flip_target"].setMinimumHeight(25) # 设置最小高度,确保按钮可见
self.buttons["mirror_target"] = QtWidgets.QPushButton("Mirror Target")
self.buttons["mirror_target"].setObjectName("mirrorTargetButton")
self.buttons["mirror_target"].setIcon(ui_utils.load_icon("mirror.png"))
self.buttons["mirror_target"].setMinimumHeight(25) # 设置最小高度,确保按钮可见
self.buttons["find_flip_target"] = QtWidgets.QPushButton("Find Flip Target")
self.buttons["find_flip_target"].setObjectName("findFlipTargetButton")
self.buttons["find_flip_target"].setIcon(ui_utils.load_icon("mirrorR.png"))
self.buttons["find_flip_target"].setMinimumHeight(25) # 设置最小高度,确保按钮可见
self.buttons["add_blendshape"] = QtWidgets.QPushButton("Add BlendShape")
self.buttons["add_blendshape"].setObjectName("addBlendshapeButton")
self.buttons["add_blendshape"].setIcon(ui_utils.load_icon("blendShape.png"))
self.buttons["add_blendshape"].setMinimumHeight(25) # 设置最小高度,确保按钮可见
self.buttons["delete_blendshape"] = QtWidgets.QPushButton("Delete BlendShape")
self.buttons["delete_blendshape"].setObjectName("deleteBlendshapeButton")
self.buttons["delete_blendshape"].setIcon(ui_utils.load_icon("blendShape.png"))
self.buttons["delete_blendshape"].setMinimumHeight(25) # 设置最小高度,确保按钮可见
self.buttons["batch_blendshape"] = QtWidgets.QPushButton("Batch BlendShape")
self.buttons["batch_blendshape"].setObjectName("batchBlendshapeButton")
self.buttons["batch_blendshape"].setIcon(ui_utils.load_icon("blendShape.png"))
self.buttons["batch_blendshape"].setMinimumHeight(25) # 设置最小高度,确保按钮可见
self.buttons["rebuild_select"] = QtWidgets.QPushButton("Rebuild Select")
self.buttons["rebuild_select"].setObjectName("rebuildSelectButton")
self.buttons["rebuild_select"].setIcon(ui_utils.load_icon("loading.png"))
self.buttons["rebuild_select"].setMinimumHeight(25) # 设置最小高度,确保按钮可见
self.buttons["reposition_joints"] = QtWidgets.QPushButton("Reposition Joints")
self.buttons["reposition_joints"].setObjectName("repositionJointsButton")
self.buttons["reposition_joints"].setIcon(ui_utils.load_icon("loading.png"))
self.buttons["reposition_joints"].setMinimumHeight(25) # 设置最小高度,确保按钮可见
self.buttons["blend_select"] = QtWidgets.QPushButton("Blend Select")
self.buttons["blend_select"].setObjectName("blendSelectButton")
self.buttons["blend_select"].setIcon(ui_utils.load_icon("loading.png"))
self.buttons["blend_select"].setMinimumHeight(25) # 设置最小高度,确保按钮可见
# 右侧Range按钮
self.buttons["bs_range_plus"] = QtWidgets.QPushButton("Range +")
self.buttons["bs_range_plus"].setObjectName("bsRangePlusButton")
self.buttons["bs_range_plus"].setIcon(ui_utils.load_icon("behaviour.png"))
self.buttons["bs_range_minus"] = QtWidgets.QPushButton("Range -")
# 右下角BS Range按钮
self.buttons["bs_range_minus"] = QtWidgets.QPushButton(get_text(" Range - ", " 范围 - "))
self.buttons["bs_range_minus"].setObjectName("bsRangeMinusButton")
self.buttons["bs_range_minus"].setIcon(ui_utils.load_icon("behaviour.png"))
self.buttons["bs_range_minus"].setMinimumWidth(0)
# 底部标签页按钮
self.buttons["tab_psd"] = QtWidgets.QPushButton("PSD")
self.buttons["tab_psd"].setIcon(ui_utils.load_icon("psd.png"))
self.buttons["tab_psd"].setObjectName("tabPsdButton")
self.buttons["tab_psd"].setCheckable(True)
self.buttons["bs_range_plus"] = QtWidgets.QPushButton(get_text(" Range + ", " 范围 + "))
self.buttons["bs_range_plus"].setObjectName("bsRangePlusButton")
self.buttons["bs_range_plus"].setIcon(ui_utils.load_icon("behaviour.png"))
self.buttons["bs_range_plus"].setMinimumWidth(0)
self.buttons["tab_bse"] = QtWidgets.QPushButton("BSE")
self.buttons["tab_bse"].setIcon(ui_utils.load_icon("blendShape.png"))
self.buttons["tab_bse"].setObjectName("tabBseButton")
self.buttons["tab_bse"].setCheckable(True)
# BlendShape操作按钮
self.buttons["flip_target"] = QtWidgets.QPushButton(get_text("Flip Target", "翻转目标"))
self.buttons["flip_target"].setObjectName("flipTargetButton")
self.buttons["flip_target"].setIcon(ui_utils.load_icon("mirrorL.png"))
self.buttons["tab_key"] = QtWidgets.QPushButton("KEY")
self.buttons["tab_key"].setIcon(ui_utils.load_icon("setKeyOnAnim.png"))
self.buttons["tab_key"].setObjectName("tabKeyButton")
self.buttons["tab_key"].setCheckable(True)
self.buttons["mirror_target"] = QtWidgets.QPushButton(get_text("Mirror Target", "镜像目标"))
self.buttons["mirror_target"].setObjectName("mirrorTargetButton")
self.buttons["mirror_target"].setIcon(ui_utils.load_icon("mirror.png"))
self.buttons["tab_mir"] = QtWidgets.QPushButton("MIR")
self.buttons["tab_mir"].setIcon(ui_utils.load_icon("mirrorR.png"))
self.buttons["tab_mir"].setObjectName("tabMirButton")
self.buttons["tab_mir"].setCheckable(True)
self.buttons["find_flip_target"] = QtWidgets.QPushButton(get_text("Find Flip Target", "查找翻转目标"))
self.buttons["find_flip_target"].setObjectName("findFlipTargetButton")
self.buttons["find_flip_target"].setIcon(ui_utils.load_icon("mirrorR.png"))
self.buttons["tab_ark"] = QtWidgets.QPushButton("ARK")
self.buttons["tab_ark"].setIcon(ui_utils.load_icon("ARKit52.png"))
self.buttons["tab_ark"].setObjectName("tabArkButton")
self.buttons["tab_ark"].setCheckable(True)
self.buttons["add_blendshape"] = QtWidgets.QPushButton(get_text("Add BlendShape", "添加BlendShape"))
self.buttons["add_blendshape"].setObjectName("addBlendshapeButton")
self.buttons["add_blendshape"].setIcon(ui_utils.load_icon("blendShape.png"))
self.buttons["tab_ctr"] = QtWidgets.QPushButton("CTR")
self.buttons["tab_ctr"].setIcon(ui_utils.load_icon("ctrl_hide.png"))
self.buttons["tab_ctr"].setObjectName("tabCtrButton")
self.buttons["tab_ctr"].setCheckable(True)
self.buttons["delete_blendshape"] = QtWidgets.QPushButton(get_text("Delete BlendShape", "删除BlendShape"))
self.buttons["delete_blendshape"].setObjectName("deleteBlendshapeButton")
self.buttons["delete_blendshape"].setIcon(ui_utils.load_icon("blendShape.png"))
# 底部主滑块
self.buttons["batch_blendshape"] = QtWidgets.QPushButton(get_text("Batch BlendShape", "批量BlendShape"))
self.buttons["batch_blendshape"].setObjectName("batchBlendshapeButton")
self.buttons["batch_blendshape"].setIcon(ui_utils.load_icon("blendShape.png"))
self.buttons["bs_range_minus"] = QtWidgets.QPushButton(get_text(" Range - ", " 范围 - "))
self.buttons["bs_range_minus"].setObjectName("bsRangeMinusButton")
self.buttons["bs_range_minus"].setIcon(ui_utils.load_icon("behaviour.png"))
self.buttons["bs_range_minus"].setMinimumWidth(0)
self.buttons["bs_range_plus"] = QtWidgets.QPushButton(get_text(" Range + ", " 范围 + "))
self.buttons["bs_range_plus"].setObjectName("bsRangePlusButton")
self.buttons["bs_range_plus"].setIcon(ui_utils.load_icon("behaviour.png"))
self.buttons["bs_range_plus"].setMinimumWidth(0)
self.buttons["rebuild_select"] = QtWidgets.QPushButton(get_text("Rebuild Select", "重建选择"))
self.buttons["rebuild_select"].setObjectName("rebuildSelectButton")
self.buttons["rebuild_select"].setIcon(ui_utils.load_icon("loading.png"))
self.buttons["reposition_joints"] = QtWidgets.QPushButton(get_text("Reposition Joints", "重新定位关节"))
self.buttons["reposition_joints"].setObjectName("repositionJointsButton")
self.buttons["reposition_joints"].setIcon(ui_utils.load_icon("loading.png"))
self.buttons["blend_select"] = QtWidgets.QPushButton(get_text("Blend Select", "混合选择"))
self.buttons["blend_select"].setObjectName("blendSelectButton")
self.buttons["blend_select"].setIcon(ui_utils.load_icon("loading.png"))
# 创建底部主滑块
self.controls["bottom_main_slider"] = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.controls["bottom_main_slider"].setObjectName("bottomMainSlider")
self.controls["bottom_main_slider"].setMinimum(0)
self.controls["bottom_main_slider"].setMaximum(100)
self.controls["bottom_main_slider"].setValue(0)
self.controls["bottom_main_slider"].setFixedHeight(20)
# 底部主滑块布局包含数值显示和All勾选框
self.layouts["bottom_slider_layout"] = QtWidgets.QHBoxLayout()
self.layouts["bottom_slider_layout"].setContentsMargins(0, 0, 0, 0)
self.layouts["bottom_slider_layout"].setSpacing(2)
# 添加数值显示标签
# 添加底部主滑块数值显示标签 - 修改命名以匹配功能代码
self.controls["bottom_slider_value"] = QtWidgets.QLabel("0.000")
self.controls["bottom_slider_value"].setObjectName("bottomSliderValue")
self.controls["bottom_slider_value"].setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.controls["bottom_slider_value"].setFixedWidth(40)
self.layouts["bottom_slider_layout"].addWidget(self.controls["bottom_slider_value"])
# 添加滑块
self.layouts["bottom_slider_layout"].addWidget(self.controls["bottom_main_slider"])
# 添加"All"勾选框
# 添加底部主滑块"All"勾选框 - 修改命名以匹配功能代码
self.controls["bottom_slider_all_check"] = QtWidgets.QCheckBox("All")
self.controls["bottom_slider_all_check"].setObjectName("bottomSliderAllCheck")
self.layouts["bottom_slider_layout"].addWidget(self.controls["bottom_slider_all_check"])
# 底部标签页按钮
self.buttons["tab_psd"] = QtWidgets.QPushButton(get_text("PSD", "PSD"))
self.buttons["tab_psd"].setIcon(ui_utils.load_icon("psd.png"))
self.buttons["tab_psd"].setObjectName("tabPsdButton")
self.buttons["tab_psd"].setCheckable(True)
self.buttons["tab_psd"].setChecked(True)
self.buttons["tab_bse"] = QtWidgets.QPushButton(get_text("BSE", "BSE"))
self.buttons["tab_bse"].setIcon(ui_utils.load_icon("blendShape.png"))
self.buttons["tab_bse"].setObjectName("tabBseButton")
self.buttons["tab_bse"].setCheckable(True)
self.buttons["tab_bse"].setChecked(True)
self.buttons["tab_key"] = QtWidgets.QPushButton(get_text("KEY", "KEY"))
self.buttons["tab_key"].setIcon(ui_utils.load_icon("setKeyOnAnim.png"))
self.buttons["tab_key"].setObjectName("tabKeyButton")
self.buttons["tab_key"].setCheckable(True)
self.buttons["tab_key"].setChecked(True)
self.buttons["tab_mir"] = QtWidgets.QPushButton(get_text("MIR", "MIR"))
self.buttons["tab_mir"].setIcon(ui_utils.load_icon("mirrorR.png"))
self.buttons["tab_mir"].setObjectName("tabMirButton")
self.buttons["tab_mir"].setCheckable(True)
self.buttons["tab_mir"].setChecked(True)
self.buttons["tab_ark"] = QtWidgets.QPushButton(get_text("ARK", "ARK"))
self.buttons["tab_ark"].setIcon(ui_utils.load_icon("ARKit52.png"))
self.buttons["tab_ark"].setObjectName("tabArkButton")
self.buttons["tab_ark"].setCheckable(True)
self.buttons["tab_ark"].setChecked(True)
self.buttons["tab_ctr"] = QtWidgets.QPushButton(get_text("CTR", "CTR"))
self.buttons["tab_ctr"].setIcon(ui_utils.load_icon("ctrl_hide.png"))
self.buttons["tab_ctr"].setObjectName("tabCtrButton")
self.buttons["tab_ctr"].setCheckable(True)
self.buttons["tab_ctr"].setChecked(True)
# 底部功能按钮
self.buttons["reset_default_expression"] = QtWidgets.QPushButton("Reset Default Expression")
self.buttons["reset_default_expression"] = QtWidgets.QPushButton(get_text("Reset Default", "重置默认"))
self.buttons["reset_default_expression"].setIcon(ui_utils.load_icon("reset.png"))
self.buttons["reset_default_expression"].setObjectName("resetDefaultExpressionButton")
self.buttons["find_select_expression"] = QtWidgets.QPushButton("Find Select Expression")
self.buttons["find_select_expression"] = QtWidgets.QPushButton(get_text("Find Select", "查找选择"))
self.buttons["find_select_expression"].setIcon(ui_utils.load_icon("expressions_current.png"))
self.buttons["find_select_expression"].setObjectName("findSelectExpressionButton")
self.buttons["write_current_expressions"] = QtWidgets.QPushButton("Write Current Expressions")
self.buttons["write_current_expressions"] = QtWidgets.QPushButton(get_text("Write Current", "写入当前"))
self.buttons["write_current_expressions"].setIcon(ui_utils.load_icon("expression.png"))
self.buttons["write_current_expressions"].setObjectName("writeCurrentExpressionsButton")
self.buttons["controller_find"] = QtWidgets.QPushButton("Controller Find")
self.buttons["controller_find"] = QtWidgets.QPushButton(get_text("Controller Find", "控制器查找"))
self.buttons["controller_find"].setIcon(ui_utils.load_icon("controller.png"))
self.buttons["controller_find"].setObjectName("controllerFindButton")
self.buttons["select_associated_joint"] = QtWidgets.QPushButton("Select Associated Joint")
self.buttons["select_associated_joint"] = QtWidgets.QPushButton(get_text("Select Joint", "选择关节"))
self.buttons["select_associated_joint"].setIcon(ui_utils.load_icon("out_joint.png"))
self.buttons["select_associated_joint"].setObjectName("selectAssociatedJointButton")
self.buttons["write_find_mirror"] = QtWidgets.QPushButton("Write Find Mirror")
self.buttons["write_find_mirror"] = QtWidgets.QPushButton(get_text("Find Mirror", "查找镜像"))
self.buttons["write_find_mirror"].setIcon(ui_utils.load_icon("mirror.png"))
self.buttons["write_find_mirror"].setObjectName("writeFindMirrorButton")
# 设置所有按钮的最小宽度为0
for button_name in ["flip_target", "mirror_target", "find_flip_target",
"add_blendshape", "delete_blendshape", "batch_blendshape",
"rebuild_select", "reposition_joints", "blend_select",
"range_minus", "range_plus", "bs_range_minus", "bs_range_plus",
"page_all", "page_2", "page_3", "page_4", "page_5", "page_6"]:
if button_name in self.buttons:
self.buttons[button_name].setMinimumWidth(0)
#========================================= LAYOUT =======================================
def create_layouts(self):
"""
创建行为系统UI布局
包括主布局、左右面板布局等
包括主布局、左右面板布局、滑块布局
"""
# 主布局
# 创建主布局
self.layouts["main_layout"] = QtWidgets.QVBoxLayout(self.main_widget)
self.layouts["main_layout"].setContentsMargins(0, 0, 0, 0)
self.layouts["main_layout"].setSpacing(0)
self.layouts["main_layout"].setContentsMargins(2, 2, 2, 2) # 减小边距
self.layouts["main_layout"].setSpacing(2) # 减小间距
# 添加标题标签
# 添加标题标签到主布局
self.layouts["main_layout"].addWidget(self.controls["title_label"])
# 创建主分割器
self.splitters["main_splitter"] = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
self.splitters["main_splitter"].setObjectName("behaviourMainSplitter")
self.splitters["main_splitter"].setHandleWidth(6) # 设置分割器手柄宽度
self.splitters["main_splitter"].setChildrenCollapsible(False) # 禁止子部件折叠
self.splitters["main_splitter"].setOpaqueResize(True) # 实时显示调整效果,更丝滑
# 添加主分割器到主布局 - 设置伸缩因子为1使其占据所有可用空间
self.layouts["main_layout"].addWidget(self.splitters["main_splitter"], 1)
# 添加主分割器
self.layouts["main_layout"].addWidget(self.splitters["main_splitter"], 1) # 设置伸缩因子为1使其占据剩余空间
# 左侧面板
self.controls["left_panel"] = QtWidgets.QWidget()
self.controls["left_panel"].setObjectName("behaviourLeftPanel")
self.controls["left_panel"].setMinimumWidth(0)
self.controls["left_panel"].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# 右侧面板
self.controls["right_panel"] = QtWidgets.QWidget()
self.controls["right_panel"].setObjectName("behaviourRightPanel")
self.controls["right_panel"].setMinimumWidth(0)
self.controls["right_panel"].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# 将左右面板添加到分割器
self.splitters["main_splitter"].addWidget(self.controls["left_panel"])
self.splitters["main_splitter"].addWidget(self.controls["right_panel"])
# 左侧面板布局
# 创建左侧面板布局
self.layouts["left_layout"] = QtWidgets.QVBoxLayout(self.controls["left_panel"])
self.layouts["left_layout"].setContentsMargins(5, 5, 5, 5)
self.layouts["left_layout"].setSpacing(5)
self.layouts["left_layout"].setContentsMargins(2, 2, 2, 2) # 减小边距
self.layouts["left_layout"].setSpacing(2) # 减小间距
# 右侧面板布局
# 创建右侧面板布局
self.layouts["right_layout"] = QtWidgets.QVBoxLayout(self.controls["right_panel"])
self.layouts["right_layout"].setContentsMargins(5, 5, 5, 5)
self.layouts["right_layout"].setSpacing(5)
self.layouts["right_layout"].setContentsMargins(2, 2, 2, 2) # 减小边距
self.layouts["right_layout"].setSpacing(2) # 减小间距
# 左侧面板控件 - Raw Control
self.controls["raw_control_group"] = QtWidgets.QGroupBox("Raw Control")
self.controls["raw_control_group"].setObjectName("rawControlGroup")
self.controls["raw_control_group"].setMinimumWidth(0) # 设置最小宽度为零
self.controls["raw_control_group"].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# 创建左侧面板内容布局 - 不使用GroupBox直接使用垂直布局
self.layouts["left_content_layout"] = QtWidgets.QVBoxLayout()
self.layouts["left_content_layout"].setContentsMargins(0, 0, 0, 0)
self.layouts["left_content_layout"].setSpacing(5)
# 左侧面板控件布局
self.layouts["raw_control_layout"] = QtWidgets.QVBoxLayout()
self.layouts["raw_control_layout"].setContentsMargins(2, 2, 2, 2)
self.layouts["raw_control_layout"].setSpacing(2)
self.controls["raw_control_group"].setLayout(self.layouts["raw_control_layout"])
# 添加标题标签
left_title = QtWidgets.QLabel(get_text("Raw Control", "原始控制"))
left_title.setStyleSheet("font-weight: bold;")
self.layouts["left_content_layout"].addWidget(left_title)
# 添加控制列表到Raw Control布局
self.layouts["raw_control_layout"].addWidget(self.controls["control_list"])
# 添加搜索框
self.layouts["left_content_layout"].addWidget(self.controls["search_input"])
# 左侧滑块布局
self.layouts["raw_control_layout"].addLayout(self.layouts["raw_slider_layout"])
# 添加控制列表 - 设置伸缩因子为1使其占据所有可用空间
self.layouts["left_content_layout"].addWidget(self.controls["control_list"], 1)
# 创建滑块布局
self.layouts["raw_slider_layout"] = QtWidgets.QHBoxLayout()
self.layouts["raw_slider_layout"].setContentsMargins(0, 0, 0, 0)
self.layouts["raw_slider_layout"].setSpacing(2)
self.layouts["raw_slider_layout"].addWidget(self.controls["raw_slider_value"])
self.layouts["raw_slider_layout"].addWidget(self.controls["raw_slider"])
self.layouts["raw_slider_layout"].addWidget(self.controls["raw_slider_all_check"])
# 添加滑块布局
self.layouts["left_content_layout"].addLayout(self.layouts["raw_slider_layout"])
# 范围调整按钮布局
self.layouts["range_buttons_layout"] = QtWidgets.QHBoxLayout()
@@ -477,14 +445,14 @@ class BehaviourUI(ui_utils.BaseUI):
# 设置Range按钮的策略使其均等撑满一行
size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
self.buttons["range_plus"].setSizePolicy(size_policy)
self.buttons["range_minus"].setSizePolicy(size_policy)
self.buttons["range_plus"].setSizePolicy(size_policy)
self.layouts["range_buttons_layout"].addWidget(self.buttons["range_plus"])
self.layouts["range_buttons_layout"].addWidget(self.buttons["range_minus"])
self.layouts["range_buttons_layout"].addWidget(self.buttons["range_plus"])
# 添加范围按钮布局到Raw Control布局
self.layouts["raw_control_layout"].addLayout(self.layouts["range_buttons_layout"])
self.layouts["left_content_layout"].addLayout(self.layouts["range_buttons_layout"])
# 页码按钮布局
self.layouts["page_buttons_layout"] = QtWidgets.QHBoxLayout()
@@ -492,38 +460,44 @@ class BehaviourUI(ui_utils.BaseUI):
self.layouts["page_buttons_layout"].setSpacing(2)
# 设置页码按钮的策略,使其均等撑满一行
for button_name in ["prev_page", "next_page", "page_all", "page_2", "page_3", "page_4", "page_5", "page_6"]:
for button_name in [ "page_all", "page_2", "page_3", "page_4", "page_5", "page_6"]:
if button_name in self.buttons:
self.buttons[button_name].setSizePolicy(size_policy)
# 添加页码按钮到布局
for button_name in ["prev_page", "next_page", "page_all", "page_2", "page_3", "page_4", "page_5", "page_6"]:
for button_name in ["page_all", "page_2", "page_3", "page_4", "page_5", "page_6"]:
if button_name in self.buttons:
self.layouts["page_buttons_layout"].addWidget(self.buttons[button_name])
# 添加页码按钮布局到Raw Control布局
self.layouts["raw_control_layout"].addLayout(self.layouts["page_buttons_layout"])
self.layouts["left_content_layout"].addLayout(self.layouts["page_buttons_layout"])
# 添加Raw Control组到左侧面板
self.layouts["left_layout"].addWidget(self.controls["raw_control_group"])
# 添加左侧内容布局到左侧面板布局 - 设置伸缩因子为1使其占据所有可用空间
self.layouts["left_layout"].addLayout(self.layouts["left_content_layout"], 1)
# 右侧面板控件 - Related BlendShapes
self.controls["blendshapes_group"] = QtWidgets.QGroupBox("Related BlendShapes")
self.controls["blendshapes_group"].setObjectName("blendshapesGroup")
self.controls["blendshapes_group"].setMinimumWidth(0) # 设置最小宽度为零
self.controls["blendshapes_group"].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# 创建右侧面板内容布局 - 不使用GroupBox直接使用垂直布局
self.layouts["right_content_layout"] = QtWidgets.QVBoxLayout()
self.layouts["right_content_layout"].setContentsMargins(0, 0, 0, 0)
self.layouts["right_content_layout"].setSpacing(5)
# 右侧面板控件布局
self.layouts["blendshapes_layout"] = QtWidgets.QVBoxLayout()
self.layouts["blendshapes_layout"].setContentsMargins(2, 2, 2, 2)
self.layouts["blendshapes_layout"].setSpacing(2)
self.controls["blendshapes_group"].setLayout(self.layouts["blendshapes_layout"])
# 添加标题标签
right_title = QtWidgets.QLabel(get_text("Related BlendShapes", "相关BlendShapes"))
right_title.setStyleSheet("font-weight: bold;")
self.layouts["right_content_layout"].addWidget(right_title)
# 添加BlendShapes列表到BlendShapes布局
self.layouts["blendshapes_layout"].addWidget(self.controls["blendshapes_list"], 1) # 设置伸缩因子为1使其占据更多空间
# 添加BlendShapes列表 - 设置伸缩因子为1使其占据所有可用空间
self.layouts["right_content_layout"].addWidget(self.controls["blendshapes_list"], 1)
# 创建BS滑块布局包含数值显示和滑块
self.layouts["bs_slider_layout"] = QtWidgets.QHBoxLayout()
self.layouts["bs_slider_layout"].setContentsMargins(0, 0, 0, 0)
self.layouts["bs_slider_layout"].setSpacing(2)
self.layouts["bs_slider_layout"].addWidget(self.controls["bs_slider_value"])
self.layouts["bs_slider_layout"].addWidget(self.controls["bs_slider"])
self.layouts["bs_slider_layout"].addWidget(self.controls["bs_slider_all_check"])
# 右侧滑块布局
self.layouts["blendshapes_layout"].addLayout(self.layouts["bs_slider_layout"])
self.layouts["right_content_layout"].addLayout(self.layouts["bs_slider_layout"])
# BS范围调整按钮布局
self.layouts["bs_range_buttons_layout"] = QtWidgets.QHBoxLayout()
@@ -531,23 +505,22 @@ class BehaviourUI(ui_utils.BaseUI):
self.layouts["bs_range_buttons_layout"].setSpacing(2)
# 设置BS Range按钮的策略使其均等撑满一行
size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
self.buttons["bs_range_plus"].setSizePolicy(size_policy)
self.buttons["bs_range_minus"].setSizePolicy(size_policy)
self.buttons["bs_range_plus"].setSizePolicy(size_policy)
self.layouts["bs_range_buttons_layout"].addWidget(self.buttons["bs_range_plus"])
self.layouts["bs_range_buttons_layout"].addWidget(self.buttons["bs_range_minus"])
self.layouts["bs_range_buttons_layout"].addWidget(self.buttons["bs_range_plus"])
# 添加BS范围按钮布局到BlendShapes布局
self.layouts["blendshapes_layout"].addLayout(self.layouts["bs_range_buttons_layout"])
self.layouts["right_content_layout"].addLayout(self.layouts["bs_range_buttons_layout"])
# 为所有按钮设置统一的大小策略
# 为网格布局中的按钮设置统一策略
for button_name in ["flip_target", "mirror_target", "find_flip_target",
"add_blendshape", "delete_blendshape", "batch_blendshape",
"rebuild_select", "reposition_joints", "blend_select"]:
if button_name in self.buttons:
self.buttons[button_name].setMinimumWidth(0)
self.buttons[button_name].setMinimumHeight(25) # 设置最小高度,确保按钮可见
# 允许按钮缩小到极小
self.buttons[button_name].setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
# BlendShape操作按钮网格
@@ -567,10 +540,10 @@ class BehaviourUI(ui_utils.BaseUI):
self.layouts["bs_buttons_grid"].addWidget(self.buttons["blend_select"], 2, 2)
# 添加按钮网格到BlendShapes布局
self.layouts["blendshapes_layout"].addLayout(self.layouts["bs_buttons_grid"])
self.layouts["right_content_layout"].addLayout(self.layouts["bs_buttons_grid"])
# 添加BlendShapes组到右侧面板
self.layouts["right_layout"].addWidget(self.controls["blendshapes_group"], 1) # 设置伸缩因子为1使其占据更多空间
# 添加右侧内容布局到右侧面板布局 - 设置伸缩因子为1使其占据所有可用空间
self.layouts["right_layout"].addLayout(self.layouts["right_content_layout"], 1)
# 底部标签页按钮行
self.layouts["tab_buttons_layout"] = QtWidgets.QHBoxLayout()
@@ -613,66 +586,85 @@ class BehaviourUI(ui_utils.BaseUI):
self.layouts["bottom_buttons_layout"].addLayout(self.layouts["bottom_buttons_row1"])
self.layouts["bottom_buttons_layout"].addLayout(self.layouts["bottom_buttons_row2"])
# 添加底部按钮布局到主布局
# 添加底部主滑块
self.layouts["bottom_main_slider_layout"] = QtWidgets.QHBoxLayout()
self.layouts["bottom_main_slider_layout"].setContentsMargins(5, 5, 5, 5)
self.layouts["bottom_main_slider_layout"].setSpacing(5)
self.layouts["bottom_main_slider_layout"].addWidget(self.controls["bottom_slider_value"])
self.layouts["bottom_main_slider_layout"].addWidget(self.controls["bottom_main_slider"])
self.layouts["bottom_main_slider_layout"].addWidget(self.controls["bottom_slider_all_check"])
# 添加底部按钮布局和主滑块到主布局
self.layouts["main_layout"].addLayout(self.layouts["bottom_buttons_layout"])
self.layouts["main_layout"].addLayout(self.layouts["bottom_main_slider_layout"])
# 底部主滑块布局
self.layouts["main_layout"].addLayout(self.layouts["bottom_slider_layout"])
# 将面板添加到分割器
self.splitters["main_splitter"].addWidget(self.controls["left_panel"])
self.splitters["main_splitter"].addWidget(self.controls["right_panel"])
# 改变分割器处理大小变化的方式
self.splitters["main_splitter"].setStretchFactor(0, 1)
self.splitters["main_splitter"].setStretchFactor(1, 1)
# 在最后设置分割器比例
QtCore.QTimer.singleShot(100, lambda: ui_utils.set_splitter_proportions(self.splitters["main_splitter"], [0.5, 0.5]))
# 设置网格布局特性
self.layouts["bs_buttons_grid"].setHorizontalSpacing(1)
self.layouts["bs_buttons_grid"].setVerticalSpacing(1)
self.layouts["bs_buttons_grid"].setColumnMinimumWidth(0, 0)
self.layouts["bs_buttons_grid"].setColumnMinimumWidth(1, 0)
self.layouts["bs_buttons_grid"].setColumnMinimumWidth(2, 0)
self.layouts["bs_buttons_grid"].setColumnStretch(0, 1)
self.layouts["bs_buttons_grid"].setColumnStretch(1, 1)
self.layouts["bs_buttons_grid"].setColumnStretch(2, 1)
#======================================= CONNECTION =====================================
def create_connections(self):
"""
创建信号连接
连接按钮点击事件和其他UI事件
"""
# 创建信号映射字典
signal_mapping = {
'buttons': {
# 底部标签页按钮
'tab_psd': {'signal': 'clicked', 'handler': lambda: utils_behaviour.switch_tab(0)},
'tab_bse': {'signal': 'clicked', 'handler': lambda: utils_behaviour.switch_tab(1)},
'tab_key': {'signal': 'clicked', 'handler': lambda: utils_behaviour.switch_tab(2)},
'tab_mir': {'signal': 'clicked', 'handler': lambda: utils_behaviour.switch_tab(3)},
'tab_ark': {'signal': 'clicked', 'handler': lambda: utils_behaviour.switch_tab(4)},
'tab_ctr': {'signal': 'clicked', 'handler': lambda: utils_behaviour.switch_tab(5)},
# 底部标签页按钮连接
self.buttons["tab_psd"].clicked.connect(lambda: utils_behaviour.switch_tab(0))
self.buttons["tab_bse"].clicked.connect(lambda: utils_behaviour.switch_tab(1))
self.buttons["tab_key"].clicked.connect(lambda: utils_behaviour.switch_tab(2))
self.buttons["tab_mir"].clicked.connect(lambda: utils_behaviour.switch_tab(3))
self.buttons["tab_ark"].clicked.connect(lambda: utils_behaviour.switch_tab(4))
self.buttons["tab_ctr"].clicked.connect(lambda: utils_behaviour.switch_tab(5))
# 功能按钮
'flip_target': {'signal': 'clicked', 'handler': utils_behaviour.flip_target},
'mirror_target': {'signal': 'clicked', 'handler': utils_behaviour.mirror_target},
'find_flip_target': {'signal': 'clicked', 'handler': utils_behaviour.find_flip_target},
'add_blendshape': {'signal': 'clicked', 'handler': utils_behaviour.add_blendshape},
'delete_blendshape': {'signal': 'clicked', 'handler': utils_behaviour.delete_blendshape},
'batch_blendshape': {'signal': 'clicked', 'handler': utils_behaviour.batch_blendshape},
'rebuild_select': {'signal': 'clicked', 'handler': utils_behaviour.rebuild_select},
'reposition_joints': {'signal': 'clicked', 'handler': utils_behaviour.reposition_joints},
'blend_select': {'signal': 'clicked', 'handler': utils_behaviour.blend_select},
'reset_default_expression': {'signal': 'clicked', 'handler': utils_behaviour.reset_default_expression},
'find_select_expression': {'signal': 'clicked', 'handler': utils_behaviour.find_select_expression},
'write_current_expressions': {'signal': 'clicked', 'handler': utils_behaviour.write_current_expressions},
'controller_find': {'signal': 'clicked', 'handler': utils_behaviour.controller_find},
'select_associated_joint': {'signal': 'clicked', 'handler': utils_behaviour.select_associated_joint},
'write_find_mirror': {'signal': 'clicked', 'handler': utils_behaviour.write_find_mirror},
# 功能按钮连接
self.buttons["flip_target"].clicked.connect(utils_behaviour.flip_target)
self.buttons["mirror_target"].clicked.connect(utils_behaviour.mirror_target)
self.buttons["find_flip_target"].clicked.connect(utils_behaviour.find_flip_target)
self.buttons["add_blendshape"].clicked.connect(utils_behaviour.add_blendshape)
self.buttons["delete_blendshape"].clicked.connect(utils_behaviour.delete_blendshape)
self.buttons["batch_blendshape"].clicked.connect(utils_behaviour.batch_blendshape)
self.buttons["rebuild_select"].clicked.connect(utils_behaviour.rebuild_select)
self.buttons["reposition_joints"].clicked.connect(utils_behaviour.reposition_joints)
self.buttons["blend_select"].clicked.connect(utils_behaviour.blend_select)
# 范围按钮
'range_plus': {'signal': 'clicked', 'handler': lambda: utils_behaviour.adjust_range(0.1)},
'range_minus': {'signal': 'clicked', 'handler': lambda: utils_behaviour.adjust_range(-0.1)},
'bs_range_plus': {'signal': 'clicked', 'handler': lambda: utils_behaviour.adjust_bs_range(0.1)},
'bs_range_minus': {'signal': 'clicked', 'handler': lambda: utils_behaviour.adjust_bs_range(-0.1)},
},
'controls': {
# 滑块控件
'raw_slider': {'signal': 'valueChanged', 'handler': utils_behaviour.on_raw_slider_changed},
'bs_slider': {'signal': 'valueChanged', 'handler': utils_behaviour.on_bs_slider_changed},
'bottom_main_slider': {'signal': 'valueChanged', 'handler': utils_behaviour.on_bottom_slider_changed},
# 表情控制按钮连接
self.buttons["reset_default_expression"].clicked.connect(utils_behaviour.reset_default_expression)
self.buttons["find_select_expression"].clicked.connect(utils_behaviour.find_select_expression)
self.buttons["write_current_expressions"].clicked.connect(utils_behaviour.write_current_expressions)
self.buttons["controller_find"].clicked.connect(utils_behaviour.controller_find)
self.buttons["select_associated_joint"].clicked.connect(utils_behaviour.select_associated_joint)
self.buttons["write_find_mirror"].clicked.connect(utils_behaviour.write_find_mirror)
# 列表控件
'control_list': {'signal': 'itemSelectionChanged', 'handler': utils_behaviour.on_control_selection_changed},
'blendshapes_list': {'signal': 'itemSelectionChanged', 'handler': utils_behaviour.on_blendshape_selection_changed},
}
}
# 范围按钮连接
self.buttons["range_plus"].clicked.connect(lambda: utils_behaviour.adjust_range(0.1))
self.buttons["range_minus"].clicked.connect(lambda: utils_behaviour.adjust_range(-0.1))
self.buttons["bs_range_plus"].clicked.connect(lambda: utils_behaviour.adjust_bs_range(0.1))
self.buttons["bs_range_minus"].clicked.connect(lambda: utils_behaviour.adjust_bs_range(-0.1))
# 使用ui_utils中的函数连接信号
ui_utils.connect_ui_signals(self, signal_mapping)
# 滑块控件连接
self.controls["raw_slider"].valueChanged.connect(utils_behaviour.on_raw_slider_changed)
self.controls["bs_slider"].valueChanged.connect(utils_behaviour.on_bs_slider_changed)
self.controls["bottom_main_slider"].valueChanged.connect(utils_behaviour.on_bottom_slider_changed)
# 列表控件连接
self.controls["control_list"].itemSelectionChanged.connect(utils_behaviour.on_control_selection_changed)
self.controls["blendshapes_list"].itemSelectionChanged.connect(utils_behaviour.on_blendshape_selection_changed)
# 连接Maya选择变化事件
ui_utils.connect_maya_selection_changed(self, lambda: utils_behaviour.on_selection_changed(self), self.main_widget)

View File

@@ -52,6 +52,7 @@ TOOL_HEIGHT = config.TOOL_HEIGHT
#========================================= LOCATION =======================================
from scripts.ui import localization
LANG = localization.LANG
get_text = localization.get_text
class DefinitionUI(ui_utils.BaseUI):
"""
@@ -59,43 +60,31 @@ class DefinitionUI(ui_utils.BaseUI):
继承自BaseUI类实现DNA定义相关的UI功能
"""
#========================================== INIT ========================================
def __init__(self):
def __init__(self, parent=None):
"""
初始化定义系统UI
创建主控件和布局,并连接信号和槽
"""
super(DefinitionUI, self).__init__()
super(DefinitionUI, self).__init__() # 不传递parent参数给BaseUI
# 设置单例实例
DefinitionUI._instance = self
# 创建主控件
self.main_widget = QtWidgets.QWidget()
self.main_widget = QtWidgets.QWidget(parent)
self.main_widget.setObjectName("definitionMainWidget")
# 初始化字典
# 初始化控件、布局和按钮字典
self.controls = {}
self.layouts = {}
self.splitters = {}
self.buttons = {}
self.splitters = {}
# 初始化UI
# 创建UI组件
self.create_widgets()
self.create_layouts()
self.create_connections()
# 添加左右面板到分割器
self.splitters["main_splitter"].addWidget(self.controls["left_panel"])
self.splitters["main_splitter"].addWidget(self.controls["right_panel"])
# 设置分割器所有子元素的最小尺寸为0确保可以自由调整
ui_utils.set_splitter_children_minimum_size(self, recursive=True)
# 设置所有控件的最小尺寸为0确保分割器可以自由移动
ui_utils.set_all_controls_minimum_size(self)
# 使用ui_utils中的函数强制设置均等大小
ui_utils.force_equal_splitter_sizes(self)
# 注意:移除了这里的分割器元素添加代码
#========================================= WIDGET =======================================
def create_widgets(self):
@@ -104,7 +93,7 @@ class DefinitionUI(ui_utils.BaseUI):
包括按钮、标签、列表等
"""
# 标题标签 - 使用HTML格式化标题
title_text = f"<h4 style='margin:0;padding:5px;'>{LANG.get('definition_title', 'DNA定义')}</h4>"
title_text = f"<h4 style='margin:0;padding:5px;'>{get_text('definition_title', 'DNA定义')}</h4>"
self.controls["title_label"] = QtWidgets.QLabel(title_text)
self.controls["title_label"].setObjectName("definitionTitleLabel")
self.controls["title_label"].setAlignment(QtCore.Qt.AlignCenter)
@@ -135,7 +124,8 @@ class DefinitionUI(ui_utils.BaseUI):
self.controls["lods_list"].setObjectName("lodsList")
# 定义LOD关联按钮
self.buttons["define_lod_relations"] = QtWidgets.QPushButton(LANG.get("define_lod_relations", "定义LOD关联"))
self.buttons["define_lod_relations"] = QtWidgets.QPushButton(get_text("define_lod_relations", "定义LOD关联"))
self.buttons["define_lod_relations"].setIcon(ui_utils.load_icon("layerEditor.png"))
self.buttons["define_lod_relations"].setObjectName("defineLodRelationsButton")
# Meshes组
@@ -147,7 +137,8 @@ class DefinitionUI(ui_utils.BaseUI):
self.controls["meshes_list"].setObjectName("meshesList")
# 创建几何体按钮
self.buttons["create_geometry"] = QtWidgets.QPushButton(LANG.get("create_geometry", "创建几何体"))
self.buttons["create_geometry"] = QtWidgets.QPushButton(get_text("create_geometry", "创建几何体"))
self.buttons["create_geometry"].setIcon(ui_utils.load_icon("polyCube.png"))
self.buttons["create_geometry"].setObjectName("createGeometryButton")
# 右侧面板控件
@@ -176,63 +167,64 @@ class DefinitionUI(ui_utils.BaseUI):
self.controls["animatedmap_list"].setObjectName("animatedmapList")
# 底部工具面板
# 入部分
self.controls["import_label"] = QtWidgets.QLabel(LANG.get("import", ""))
self.controls["import_label"].setObjectName("importLabel")
self.controls["import_label"].setAlignment(QtCore.Qt.AlignCenter)
# 入部分
self.controls["write_label"] = QtWidgets.QLabel(get_text("Write", ""))
self.controls["write_label"].setObjectName("WriteLabel")
self.controls["write_label"].setAlignment(QtCore.Qt.AlignCenter)
self.buttons["import_joint_system"] = QtWidgets.QPushButton(LANG.get("import_joint_system", "导入关节系统"))
self.buttons["import_joint_system"].setObjectName("importJointSystemButton")
self.buttons["write_neutral_pose_joint_position"] = QtWidgets.QPushButton(get_text("Write Neutral Pose Joint Position", "写入中性Pose关节位置"))
self.buttons["write_neutral_pose_joint_position"].setIcon(ui_utils.load_icon("HIKCharacterToolBodyPart.png"))
self.buttons["write_neutral_pose_joint_position"].setObjectName("WriteNeutralPoseJointPositionButton")
self.buttons["import_geometry"] = QtWidgets.QPushButton(LANG.get("import_geometry", "入几何体"))
self.buttons["import_geometry"].setObjectName("importGeometryButton")
self.buttons["write_geometry"] = QtWidgets.QPushButton(get_text("Write Geometry", "入几何体"))
self.buttons["write_geometry"].setIcon(ui_utils.load_icon("polyCube.png"))
self.buttons["write_geometry"].setObjectName("WriteGeometryButton")
self.buttons["import_blendshape_target"] = QtWidgets.QPushButton(LANG.get("import_blendshape_target", "导入形状混合目标"))
self.buttons["import_blendshape_target"].setObjectName("importBlendshapeTargetButton")
self.buttons["write_skin_weight"] = QtWidgets.QPushButton(get_text("Write Skin Weight", "写入蒙皮权重"))
self.buttons["write_skin_weight"].setIcon(ui_utils.load_icon("paintSkinWeights.png"))
self.buttons["write_skin_weight"].setObjectName("WriteSkinWeightButton")
self.buttons["import_animated_map"] = QtWidgets.QPushButton(LANG.get("import_animated_map", "导入动画贴图"))
self.buttons["import_animated_map"].setObjectName("importAnimatedMapButton")
self.buttons["write_blendshape_target"] = QtWidgets.QPushButton(get_text("Write Blendshape Target", "写入BS对象"))
self.buttons["write_blendshape_target"].setIcon(ui_utils.load_icon("blendShape.png"))
self.buttons["write_blendshape_target"].setObjectName("WriteBlendshapeTargetButton")
# 创建部分
self.controls["create_label"] = QtWidgets.QLabel(LANG.get("create", "创建"))
self.controls["create_label"].setObjectName("createLabel")
self.controls["create_label"] = QtWidgets.QLabel(get_text("Create", "创建"))
self.controls["create_label"].setObjectName("CreateLabel")
self.controls["create_label"].setAlignment(QtCore.Qt.AlignCenter)
self.buttons["create_neutral_pose"] = QtWidgets.QPushButton(LANG.get("create_neutral_pose", "创建中性姿势"))
self.buttons["create_neutral_pose"].setObjectName("createNeutralPoseButton")
self.buttons["create_blendshapes_for_mesh"] = QtWidgets.QPushButton(get_text("Create Blendshapes For Mesh", "为模型创建Blendshape"))
self.buttons["create_blendshapes_for_mesh"].setIcon(ui_utils.load_icon("blendShapeEditor.png"))
self.buttons["create_blendshapes_for_mesh"].setObjectName("CreateBlendshapeForMeshButton")
self.buttons["bind_geometry"] = QtWidgets.QPushButton(LANG.get("bind_geometry", "绑定几何体"))
self.buttons["bind_geometry"].setObjectName("bindGeometryButton")
self.buttons["create_skin_for_mesh"] = QtWidgets.QPushButton(get_text("Create Skin For Mesh", "为模型创建绑定蒙皮"))
self.buttons["create_skin_for_mesh"].setIcon(ui_utils.load_icon("smoothSkin.png"))
self.buttons["create_skin_for_mesh"].setObjectName("CreateSkinForMeshButton")
self.buttons["take_pose"] = QtWidgets.QPushButton(LANG.get("take_pose", "取消姿势"))
self.buttons["take_pose"].setObjectName("takePoseButton")
self.buttons["unbind_skin_for_mesh"] = QtWidgets.QPushButton(get_text("Unbind Skin For Mesh", "为模型取消绑定蒙皮"))
self.buttons["unbind_skin_for_mesh"].setIcon(ui_utils.load_icon("detachSkin.png"))
self.buttons["unbind_skin_for_mesh"].setObjectName("UnbindSkinForMeshButton")
# 工具部分
self.controls["tools_label"] = QtWidgets.QLabel(LANG.get("tools", "工具"))
self.controls["tools_label"].setObjectName("toolsLabel")
self.controls["tools_label"] = QtWidgets.QGroupBox(get_text("Tools", "工具"))
self.controls["tools_label"].setObjectName("ToolsLabel")
self.controls["tools_label"].setAlignment(QtCore.Qt.AlignCenter)
self.buttons["reposition_all_joints"] = QtWidgets.QPushButton(LANG.get("reposition_all_joints", "重新定位所有关节"))
self.buttons["reposition_all_joints"].setObjectName("repositionAllJointsButton")
self.buttons["new_head_netural_joint_transform"] = QtWidgets.QPushButton(get_text("New Head Netural Joint Transform", "重新定位头部关节"))
self.buttons["new_head_netural_joint_transform"].setIcon(ui_utils.load_icon("HIKCharacterToolSkeleton.png"))
self.buttons["new_head_netural_joint_transform"].setObjectName("NewHeadNeturalJointTransformButton")
self.buttons["reposition_selected_joints"] = QtWidgets.QPushButton(LANG.get("reposition_selected_joints", "重新定位选定关节"))
self.buttons["reposition_selected_joints"].setObjectName("repositionSelectedJointsButton")
self.buttons["new_body_netural_joint_transform"] = QtWidgets.QPushButton(get_text("New Body Netural Joint Transform", "重新定位身体关节"))
self.buttons["new_body_netural_joint_transform"].setIcon(ui_utils.load_icon("HIKCharacterToolSkeleton.png"))
self.buttons["new_body_netural_joint_transform"].setObjectName("NewBodyNeturalJointTransformButton")
self.buttons["reposition_all_head_joints"] = QtWidgets.QPushButton(LANG.get("reposition_all_head_joints", "重新定位所有头部关节"))
self.buttons["reposition_all_head_joints"].setObjectName("repositionAllHeadJointsButton")
self.buttons["new_netural_joint_transform"] = QtWidgets.QPushButton(get_text("New Netural Joint Transform", "重新定位全身关节"))
self.buttons["new_netural_joint_transform"].setIcon(ui_utils.load_icon("HIKCharacterToolSkeleton.png"))
self.buttons["new_netural_joint_transform"].setObjectName("NewNeturalJointTransformButton")
self.buttons["quick_preset"] = QtWidgets.QPushButton(LANG.get("quick_preset", "快速预设"))
self.buttons["quick_preset"].setObjectName("quickPresetButton")
self.buttons["unbind_skin"] = QtWidgets.QPushButton(LANG.get("unbind_skin", "取消蒙皮"))
# 工具组
self.controls["tools_group"] = QtWidgets.QGroupBox(LANG.get("tools_group", "工具"))
self.controls["tools_group"].setObjectName("toolsGroup")
# 工具按钮
self.buttons["reposition_head_joints"] = QtWidgets.QPushButton(LANG.get("reposition_head_joints", "重新定位头部关节"))
self.buttons["reposition_body_joints"] = QtWidgets.QPushButton(LANG.get("reposition_body_joints", "重新定位身体关节"))
self.buttons["reposition_all_joints"] = QtWidgets.QPushButton(LANG.get("reposition_all_joints", "重新定位全身关节"))
self.buttons["quick_preset"] = QtWidgets.QPushButton(LANG.get("quick_preset", "快速创建预设"))
self.buttons["quick_create_preset"] = QtWidgets.QPushButton(get_text("Quick Create Preset", "快速创建预设"))
self.buttons["quick_create_preset"].setIcon(ui_utils.load_icon("QR_QuickRigTool.png"))
self.buttons["quick_create_preset"].setObjectName("QuickCreatePreset")
#========================================= LAYOUT =======================================
def create_layouts(self):
@@ -240,22 +232,16 @@ class DefinitionUI(ui_utils.BaseUI):
创建定义系统UI布局
组织控件的排列和层次结构
"""
# 主布局
# 创建主布局
self.layouts["main_layout"] = QtWidgets.QVBoxLayout(self.main_widget)
self.layouts["main_layout"].setContentsMargins(5, 5, 5, 5)
self.layouts["main_layout"].setSpacing(5)
# 添加标题标签
# 添加标题标签到主布局
self.layouts["main_layout"].addWidget(self.controls["title_label"])
# 主分割器布局
self.layouts["main_splitter_layout"] = QtWidgets.QHBoxLayout()
self.layouts["main_splitter_layout"].setContentsMargins(0, 0, 0, 0)
self.layouts["main_splitter_layout"].setSpacing(0)
self.layouts["main_splitter_layout"].addWidget(self.splitters["main_splitter"])
# 添加主分割器布局到主布局
self.layouts["main_layout"].addLayout(self.layouts["main_splitter_layout"])
# 添加主分割器到主布局
self.layouts["main_layout"].addWidget(self.splitters["main_splitter"])
# 左侧面板布局
self.layouts["left_layout"] = QtWidgets.QVBoxLayout(self.controls["left_panel"])
@@ -337,41 +323,56 @@ class DefinitionUI(ui_utils.BaseUI):
self.layouts["bottom_panel"].setContentsMargins(0, 10, 0, 0)
self.layouts["bottom_panel"].setSpacing(10)
# 入部分布局
self.layouts["import_layout"] = QtWidgets.QVBoxLayout()
self.layouts["import_layout"].setSpacing(5)
self.layouts["import_layout"].addWidget(self.controls["import_label"])
self.layouts["import_layout"].addWidget(self.buttons["import_joint_system"])
self.layouts["import_layout"].addWidget(self.buttons["import_geometry"])
self.layouts["import_layout"].addWidget(self.buttons["import_blendshape_target"])
self.layouts["import_layout"].addWidget(self.buttons["import_animated_map"])
# 入部分QGroupBox
self.controls["write_group"] = QtWidgets.QGroupBox(get_text("Write", "写入"))
self.controls["write_group"].setObjectName("writeGroup")
self.layouts["write_layout"] = QtWidgets.QVBoxLayout()
self.layouts["write_layout"].setSpacing(5)
self.layouts["write_layout"].addWidget(self.buttons["write_neutral_pose_joint_position"])
self.layouts["write_layout"].addWidget(self.buttons["write_geometry"])
self.layouts["write_layout"].addWidget(self.buttons["write_skin_weight"])
self.layouts["write_layout"].addWidget(self.buttons["write_blendshape_target"])
self.layouts["write_layout"].addStretch(1)
self.controls["write_group"].setLayout(self.layouts["write_layout"])
# 创建部分布局
# 创建部分QGroupBox
self.controls["create_group"] = QtWidgets.QGroupBox(get_text("Create", "创建"))
self.controls["create_group"].setObjectName("createGroup")
self.layouts["create_layout"] = QtWidgets.QVBoxLayout()
self.layouts["create_layout"].setSpacing(5)
self.layouts["create_layout"].addWidget(self.controls["create_label"])
self.layouts["create_layout"].addWidget(self.buttons["create_neutral_pose"])
self.layouts["create_layout"].addWidget(self.buttons["bind_geometry"])
self.layouts["create_layout"].addWidget(self.buttons["take_pose"])
self.layouts["create_layout"].addWidget(self.buttons["unbind_skin"])
self.layouts["create_layout"].addWidget(self.buttons["create_blendshapes_for_mesh"])
self.layouts["create_layout"].addWidget(self.buttons["create_skin_for_mesh"])
self.layouts["create_layout"].addWidget(self.buttons["unbind_skin_for_mesh"])
self.layouts["create_layout"].addStretch(1)
self.controls["create_group"].setLayout(self.layouts["create_layout"])
# 工具部分布局
# 工具部分QGroupBox
self.controls["tools_group"] = QtWidgets.QGroupBox(get_text("Tools", "工具"))
self.controls["tools_group"].setObjectName("toolsGroup")
self.layouts["tools_layout"] = QtWidgets.QVBoxLayout()
self.layouts["tools_layout"].setSpacing(5)
self.layouts["tools_layout"].addWidget(self.controls["tools_label"])
self.layouts["tools_layout"].addWidget(self.buttons["reposition_all_joints"])
self.layouts["tools_layout"].addWidget(self.buttons["reposition_selected_joints"])
self.layouts["tools_layout"].addWidget(self.buttons["reposition_all_head_joints"])
self.layouts["tools_layout"].addWidget(self.buttons["quick_preset"])
self.layouts["tools_layout"].addWidget(self.buttons["new_head_netural_joint_transform"])
self.layouts["tools_layout"].addWidget(self.buttons["new_body_netural_joint_transform"])
self.layouts["tools_layout"].addWidget(self.buttons["new_netural_joint_transform"])
self.layouts["tools_layout"].addWidget(self.buttons["quick_create_preset"])
self.layouts["tools_layout"].addStretch(1)
self.controls["tools_group"].setLayout(self.layouts["tools_layout"])
# 添加三到底部面板
self.layouts["bottom_panel"].addLayout(self.layouts["import_layout"])
self.layouts["bottom_panel"].addLayout(self.layouts["create_layout"])
self.layouts["bottom_panel"].addLayout(self.layouts["tools_layout"])
# 添加三到底部面板
self.layouts["bottom_panel"].addWidget(self.controls["write_group"])
self.layouts["bottom_panel"].addWidget(self.controls["create_group"])
self.layouts["bottom_panel"].addWidget(self.controls["tools_group"])
# 添加底部面板到主布局
self.layouts["main_layout"].addLayout(self.layouts["bottom_panel"])
# 添加左右面板到分割器 - 移动到这里
self.splitters["main_splitter"].addWidget(self.controls["left_panel"])
self.splitters["main_splitter"].addWidget(self.controls["right_panel"])
# 强制更新分割器大小 - 添加这一行,使用比例设置
QtCore.QTimer.singleShot(100, lambda: ui_utils.set_splitter_proportions(self.splitters["main_splitter"], [0.5, 0.5]))
#======================================= CONNECTION =====================================
def create_connections(self):
"""
@@ -408,19 +409,18 @@ class DefinitionUI(ui_utils.BaseUI):
# 底部工具面板连接
# 导入部分按钮连接
self.buttons["import_joint_system"].clicked.connect(utils_definition.import_joint_system)
self.buttons["import_geometry"].clicked.connect(utils_definition.import_geometry)
self.buttons["import_blendshape_target"].clicked.connect(utils_definition.import_blendshape_target)
self.buttons["import_animated_map"].clicked.connect(utils_definition.import_animated_map)
self.buttons["write_neutral_pose_joint_position"].clicked.connect(utils_definition.write_neutral_pose_joint_position)
self.buttons["write_geometry"].clicked.connect(utils_definition.write_geometry)
self.buttons["write_skin_weight"].clicked.connect(utils_definition.write_skin_weight)
self.buttons["write_blendshape_target"].clicked.connect(utils_definition.write_blendshape_target)
# 创建部分按钮连接
self.buttons["create_neutral_pose"].clicked.connect(utils_definition.create_neutral_pose)
self.buttons["bind_geometry"].clicked.connect(utils_definition.bind_geometry)
self.buttons["take_pose"].clicked.connect(utils_definition.take_pose)
self.buttons["unbind_skin"].clicked.connect(utils_definition.unbind_skin)
self.buttons["create_blendshapes_for_mesh"].clicked.connect(utils_definition.create_blendshapes_for_mesh)
self.buttons["create_skin_for_mesh"].clicked.connect(utils_definition.create_skin_for_mesh)
self.buttons["unbind_skin_for_mesh"].clicked.connect(utils_definition.unbind_skin_for_mesh)
# 工具部分按钮连接
self.buttons["reposition_all_joints"].clicked.connect(utils_definition.reposition_all_joints)
self.buttons["reposition_selected_joints"].clicked.connect(utils_definition.reposition_selected_joints)
self.buttons["reposition_all_head_joints"].clicked.connect(utils_definition.reposition_all_head_joints)
self.buttons["quick_preset"].clicked.connect(utils_definition.quick_preset)
self.buttons["new_head_netural_joint_transform"].clicked.connect(utils_definition.new_head_netural_joint_transform)
self.buttons["new_body_netural_joint_transform"].clicked.connect(utils_definition.new_body_netural_joint_transform)
self.buttons["new_netural_joint_transform"].clicked.connect(utils_definition.new_netural_joint_transform)
self.buttons["quick_create_preset"].clicked.connect(utils_definition.quick_create_preset)

View File

@@ -57,6 +57,7 @@ TOOL_HEIGHT = config.TOOL_HEIGHT
#========================================= LOCATION =======================================
from scripts.ui import localization
LANG = localization.LANG
get_text = localization.get_text
class GeometryUI(ui_utils.BaseUI):
"""
@@ -87,7 +88,7 @@ class GeometryUI(ui_utils.BaseUI):
包括按钮、标签、列表等
"""
# 标题标签 - 使用HTML格式化标题
title_text = f"<h4 style='margin:0;padding:5px;'>{LANG.get('geometry_title', '几何模型')}</h4>"
title_text = f"<h4 style='margin:0;padding:5px;'>{get_text('geometry_title', '几何模型')}</h4>"
self.controls["title_label"] = QtWidgets.QLabel(title_text)
self.controls["title_label"].setObjectName("geometryTitleLabel")
self.controls["title_label"].setAlignment(QtCore.Qt.AlignCenter)
@@ -112,29 +113,29 @@ class GeometryUI(ui_utils.BaseUI):
self.controls["model_list"].setObjectName("modelList")
# 模型操作按钮
self.buttons["add_model"] = QtWidgets.QPushButton(LANG.get("add_model", "添加模型"))
self.buttons["add_model"] = QtWidgets.QPushButton(get_text("add_model", "添加模型"))
self.buttons["add_model"].setObjectName("addModelButton")
self.buttons["remove_model"] = QtWidgets.QPushButton(LANG.get("remove_model", "移除模型"))
self.buttons["remove_model"] = QtWidgets.QPushButton(get_text("remove_model", "移除模型"))
self.buttons["remove_model"].setObjectName("removeModelButton")
self.buttons["duplicate_model"] = QtWidgets.QPushButton(LANG.get("duplicate_model", "复制模型"))
self.buttons["duplicate_model"] = QtWidgets.QPushButton(get_text("duplicate_model", "复制模型"))
self.buttons["duplicate_model"].setObjectName("duplicateModelButton")
# 右侧面板控件 - 模型属性
self.controls["model_properties_group"] = QtWidgets.QGroupBox(LANG.get("model_properties", "模型属性"))
self.controls["model_properties_group"] = QtWidgets.QGroupBox(get_text("model_properties", "模型属性"))
self.controls["model_properties_group"].setObjectName("modelPropertiesGroup")
# 模型名称标签和输入框
self.controls["model_name_label"] = QtWidgets.QLabel(LANG.get("name", "名称:"))
self.controls["model_name_label"] = QtWidgets.QLabel(get_text("name", "名称:"))
self.controls["model_name_label"].setObjectName("modelNameLabel")
self.controls["model_name_input"] = QtWidgets.QLineEdit()
self.controls["model_name_input"].setObjectName("modelNameInput")
self.controls["model_name_input"].setPlaceholderText(LANG.get("enter_model_name", "输入模型名称"))
self.controls["model_name_input"].setPlaceholderText(get_text("enter_model_name", "输入模型名称"))
# 模型类型标签和下拉框
self.controls["model_type_label"] = QtWidgets.QLabel(LANG.get("type", "类型:"))
self.controls["model_type_label"] = QtWidgets.QLabel(get_text("type", "类型:"))
self.controls["model_type_label"].setObjectName("modelTypeLabel")
self.controls["model_type_combo"] = QtWidgets.QComboBox()
@@ -142,78 +143,78 @@ class GeometryUI(ui_utils.BaseUI):
self.controls["model_type_combo"].addItems(["Base", "Head", "Body", "Accessory", "Other"])
# 模型可见性复选框
self.controls["model_visible_check"] = QtWidgets.QCheckBox(LANG.get("visible", "可见"))
self.controls["model_visible_check"] = QtWidgets.QCheckBox(get_text("visible", "可见"))
self.controls["model_visible_check"].setObjectName("modelVisibleCheck")
self.controls["model_visible_check"].setChecked(True)
# 模型属性按钮
self.buttons["apply_properties"] = QtWidgets.QPushButton(LANG.get("apply", "应用"))
self.buttons["apply_properties"] = QtWidgets.QPushButton(get_text("apply", "应用"))
self.buttons["apply_properties"].setObjectName("applyPropertiesButton")
self.buttons["reset_properties"] = QtWidgets.QPushButton(LANG.get("reset", "重置"))
self.buttons["reset_properties"] = QtWidgets.QPushButton(get_text("reset", "重置"))
self.buttons["reset_properties"].setObjectName("resetPropertiesButton")
# 右侧面板控件 - 模型工具
self.controls["model_tools_group"] = QtWidgets.QGroupBox(LANG.get("model_tools", "模型工具"))
self.controls["model_tools_group"] = QtWidgets.QGroupBox(get_text("model_tools", "模型工具"))
self.controls["model_tools_group"].setObjectName("modelToolsGroup")
# 模型工具按钮
self.buttons["standardize_names"] = QtWidgets.QPushButton(LANG.get("standardize_names", "标准化命名"))
self.buttons["standardize_names"] = QtWidgets.QPushButton(get_text("standardize_names", "标准化命名"))
self.buttons["standardize_names"].setObjectName("standardizeNamesButton")
self.buttons["auto_group"] = QtWidgets.QPushButton(LANG.get("auto_group", "自动分组"))
self.buttons["auto_group"] = QtWidgets.QPushButton(get_text("auto_group", "自动分组"))
self.buttons["auto_group"].setObjectName("autoGroupButton")
self.buttons["generate_accessories"] = QtWidgets.QPushButton(LANG.get("generate_accessories", "生成配件"))
self.buttons["generate_accessories"] = QtWidgets.QPushButton(get_text("generate_accessories", "生成配件"))
self.buttons["generate_accessories"].setObjectName("generateAccessoriesButton")
self.buttons["fix_seams"] = QtWidgets.QPushButton(LANG.get("fix_seams", "修复接缝"))
self.buttons["fix_seams"] = QtWidgets.QPushButton(get_text("fix_seams", "修复接缝"))
self.buttons["fix_seams"].setObjectName("fixSeamsButton")
self.buttons["fix_vertex_order"] = QtWidgets.QPushButton(LANG.get("fix_vertex_order", "修复点序"))
self.buttons["fix_vertex_order"] = QtWidgets.QPushButton(get_text("fix_vertex_order", "修复点序"))
self.buttons["fix_vertex_order"].setObjectName("fixVertexOrderButton")
# 底部工具面板
# 导入部分
self.controls["import_group"] = QtWidgets.QGroupBox(LANG.get("import", "导入"))
self.controls["import_group"] = QtWidgets.QGroupBox(get_text("import", "导入"))
self.controls["import_group"].setObjectName("importGroup")
self.buttons["import_model"] = QtWidgets.QPushButton(LANG.get("import_model", "导入模型"))
self.buttons["import_model"] = QtWidgets.QPushButton(get_text("import_model", "导入模型"))
self.buttons["import_model"].setObjectName("importModelButton")
self.buttons["import_fbx"] = QtWidgets.QPushButton(LANG.get("import_fbx", "导入FBX"))
self.buttons["import_fbx"] = QtWidgets.QPushButton(get_text("import_fbx", "导入FBX"))
self.buttons["import_fbx"].setObjectName("importFbxButton")
self.buttons["import_obj"] = QtWidgets.QPushButton(LANG.get("import_obj", "导入OBJ"))
self.buttons["import_obj"] = QtWidgets.QPushButton(get_text("import_obj", "导入OBJ"))
self.buttons["import_obj"].setObjectName("importObjButton")
# 导出部分
self.controls["export_group"] = QtWidgets.QGroupBox(LANG.get("export", "导出"))
self.controls["export_group"] = QtWidgets.QGroupBox(get_text("export", "导出"))
self.controls["export_group"].setObjectName("exportGroup")
self.buttons["export_model"] = QtWidgets.QPushButton(LANG.get("export_model", "导出模型"))
self.buttons["export_model"] = QtWidgets.QPushButton(get_text("export_model", "导出模型"))
self.buttons["export_model"].setObjectName("exportModelButton")
self.buttons["export_fbx"] = QtWidgets.QPushButton(LANG.get("export_fbx", "导出 FBX"))
self.buttons["export_fbx"] = QtWidgets.QPushButton(get_text("export_fbx", "导出 FBX"))
self.buttons["export_fbx"].setObjectName("exportFbxButton")
self.buttons["export_obj"] = QtWidgets.QPushButton(LANG.get("export_obj", "导出 OBJ"))
self.buttons["export_obj"] = QtWidgets.QPushButton(get_text("export_obj", "导出 OBJ"))
self.buttons["export_obj"].setObjectName("exportObjButton")
# 工具部分
self.controls["tools_group"] = QtWidgets.QGroupBox(LANG.get("tools", "工具"))
self.controls["tools_group"] = QtWidgets.QGroupBox(get_text("tools", "工具"))
self.controls["tools_group"].setObjectName("toolsGroup")
self.buttons["check_model"] = QtWidgets.QPushButton(LANG.get("check_model", "检查模型"))
self.buttons["check_model"] = QtWidgets.QPushButton(get_text("check_model", "检查模型"))
self.buttons["check_model"].setObjectName("checkModelButton")
self.buttons["optimize_model"] = QtWidgets.QPushButton(LANG.get("optimize_model", "优化模型"))
self.buttons["optimize_model"] = QtWidgets.QPushButton(get_text("optimize_model", "优化模型"))
self.buttons["optimize_model"].setObjectName("optimizeModelButton")
self.buttons["clean_model"] = QtWidgets.QPushButton(LANG.get("clean_model", "清理模型"))
self.buttons["clean_model"] = QtWidgets.QPushButton(get_text("clean_model", "清理模型"))
self.buttons["clean_model"].setObjectName("cleanModelButton")
self.buttons["uv_tools"] = QtWidgets.QPushButton(LANG.get("uv_tools", "UV工具"))
self.buttons["uv_tools"] = QtWidgets.QPushButton(get_text("uv_tools", "UV工具"))
self.buttons["uv_tools"].setObjectName("uvToolsButton")
def create_lod_tabs(self):
@@ -271,29 +272,38 @@ class GeometryUI(ui_utils.BaseUI):
label.setMinimumWidth(40)
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
# 创建输入字段
input_field = QtWidgets.QLineEdit()
input_field.setObjectName(f"{lod_name}_{part}_input")
input_field.setPlaceholderText(LANG.get("enter_model_name", "输入模型名称"))
# 输入框
line_edit = QtWidgets.QLineEdit()
line_edit.setPlaceholderText(get_text("enter_model_name", "输入模型名称"))
line_edit.setObjectName(f"{lod_name}_{part}_input")
# 创建加载按钮
load_button = QtWidgets.QPushButton(LANG.get(" load ", " "))
load_button.setObjectName(f"{lod_name}_{part}_load_button")
# 创建加载按钮(统一插件风格)
load_button = QtWidgets.QPushButton(get_text(" load ", " "))
load_button.setObjectName("{TOOL_NAME}LoadButton")
load_button.setIcon(QtGui.QIcon(os.path.join(ICONS_PATH, "loading.png")))
load_button.setFixedSize(120, 24)
load_button.setToolTip(LANG.get("load_model", "加载模型"))
load_button.setIconSize(QtCore.QSize(16, 16))
load_button.setToolTip(get_text("load_model", "加载模型"))
load_button.setFixedSize(105, 24) # 更紧凑,和输入框高度完全一致
load_button.setStyleSheet("""
QPushButton {
padding: 2px 2px;
margin: 0px 5px 0px 0px; /* 上右下左的边距,增加上边距避免与标签栏重叠 */
}
""")
# 将控件添加到布局
part_layout.addWidget(label)
part_layout.addWidget(input_field)
part_layout.addWidget(line_edit, stretch=1)
part_layout.addWidget(load_button)
part_layout.setStretch(0, 0) # label 不拉伸
part_layout.setStretch(1, 1) # 输入框自适应
part_layout.setStretch(2, 0) # 按钮固定宽度
# 将布局添加到标签页布局
tab_layout.addLayout(part_layout)
# 保存控件引用
self.controls[f"{lod_name}_{part}_label"] = label
self.controls[f"{lod_name}_{part}_input"] = input_field
self.controls[f"{lod_name}_{part}_input"] = line_edit
self.buttons[f"{lod_name}_{part}_load"] = load_button
# 添加弹性空间
@@ -303,19 +313,19 @@ class GeometryUI(ui_utils.BaseUI):
self.controls["tab_widget"].addTab(tab, lod_name)
# 创建清理按钮
self.buttons["clear"] = QtWidgets.QPushButton(LANG.get("clear", " 清 理 "))
self.buttons["clear"].setObjectName("clearButton")
self.buttons["clear"].setIcon(ui_utils.load_icon("delete.png"))
self.buttons["clear"].setIconSize(QtCore.QSize(16, 16))
self.buttons["clear"].setFixedSize(150, 24) # 与加载按钮高度一致
self.buttons["clear"].setToolTip(LANG.get("clear_all_models", "清理所有模型"))
self.buttons["clear"].setStyleSheet("""
self.buttons["clean"] = QtWidgets.QPushButton(get_text("clean", " 清 理 "))
self.buttons["clean"].setObjectName("clearButton")
self.buttons["clean"].setIcon(ui_utils.load_icon("delete.png"))
self.buttons["clean"].setIconSize(QtCore.QSize(16, 16))
self.buttons["clean"].setFixedSize(150, 28)
self.buttons["clean"].setToolTip(get_text("clear_all_models", "清理所有模型"))
self.buttons["clean"].setStyleSheet("""
QPushButton {
padding: 2px 2px;
margin: 0px 50px 0px 0px; /* 上右下左的边距,增加上边距避免与标签栏重叠 */
}
""")
self.buttons["clear"].setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
self.buttons["clean"].setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
def create_bottom_buttons(self):
"""
@@ -334,7 +344,7 @@ class GeometryUI(ui_utils.BaseUI):
bottom_layout.setSpacing(5)
# 创建模型工具区域
model_tools_group = QtWidgets.QGroupBox(LANG.get("model_tools", "模型工具"))
model_tools_group = QtWidgets.QGroupBox(get_text("model_tools", "模型工具"))
model_tools_group.setObjectName("modelToolsGroup")
# 创建模型工具布局
@@ -355,7 +365,7 @@ class GeometryUI(ui_utils.BaseUI):
topology_container_layout = QtWidgets.QHBoxLayout(topology_container)
topology_container_layout.setContentsMargins(5, 5, 5, 5)
topology_label = QtWidgets.QLabel(LANG.get("topology_structure", "拓扑结构") + ":")
topology_label = QtWidgets.QLabel(get_text("topology_structure", "拓扑结构") + ":")
self.controls["topology_combo"] = QtWidgets.QComboBox()
self.controls["topology_combo"].setObjectName("topologyCombo")
self.controls["topology_combo"].addItem("MetaHuman")
@@ -369,10 +379,10 @@ class GeometryUI(ui_utils.BaseUI):
lod_container_layout = QtWidgets.QHBoxLayout(lod_container)
lod_container_layout.setContentsMargins(5, 5, 5, 5)
lod_label = QtWidgets.QLabel(LANG.get("select_lod", "选择LOD") + ":")
lod_label = QtWidgets.QLabel(get_text("select_lod", "选择LOD") + ":")
self.controls["lod_combo"] = QtWidgets.QComboBox()
self.controls["lod_combo"].setObjectName("lodCombo")
self.controls["lod_combo"].addItem(LANG.get("all", "全部"))
self.controls["lod_combo"].addItem(get_text("all", "全部"))
for i in range(8): # LOD0~LOD7
self.controls["lod_combo"].addItem(f"LOD{i}")
@@ -385,7 +395,7 @@ class GeometryUI(ui_utils.BaseUI):
button_container_layout = QtWidgets.QHBoxLayout(button_container)
button_container_layout.setContentsMargins(5, 5, 5, 5)
self.buttons["create_lod"] = QtWidgets.QPushButton(LANG.get("create_lod", "创建LOD"))
self.buttons["create_lod"] = QtWidgets.QPushButton(get_text("create_lod", "创建LOD"))
self.buttons["create_lod"].setObjectName("createLodButton")
self.buttons["create_lod"].setIcon(ui_utils.load_icon("create_lod.png"))
self.buttons["create_lod"].setMinimumHeight(30)
@@ -421,13 +431,13 @@ class GeometryUI(ui_utils.BaseUI):
self.layouts["buttons_row3_layout"].setSpacing(10)
# 第一行按钮
self.buttons["separate_model"] = QtWidgets.QPushButton(LANG.get("separate_model", "模型分离"))
self.buttons["separate_model"] = QtWidgets.QPushButton(get_text("separate_model", "模型分离"))
self.buttons["separate_model"].setObjectName("separateModelButton")
self.buttons["separate_model"].setIcon(ui_utils.load_icon("polySplitVertex.png"))
self.buttons["separate_model"].setMinimumHeight(30)
self.buttons["separate_model"].setSizePolicy(size_policy)
self.buttons["generate_face_components"] = QtWidgets.QPushButton(LANG.get("generate_face_components", "生成面部配件"))
self.buttons["generate_face_components"] = QtWidgets.QPushButton(get_text("generate_face_components", "生成面部配件"))
self.buttons["generate_face_components"].setObjectName("generateFaceComponentsButton")
self.buttons["generate_face_components"].setIcon(ui_utils.load_icon("meshes.png"))
self.buttons["generate_face_components"].setMinimumHeight(30)
@@ -438,13 +448,13 @@ class GeometryUI(ui_utils.BaseUI):
self.layouts["buttons_row1_layout"].addWidget(self.buttons["generate_face_components"])
# 第二行按钮
self.buttons["fix_normals"] = QtWidgets.QPushButton(LANG.get("fix_normals", "修复法线"))
self.buttons["fix_normals"] = QtWidgets.QPushButton(get_text("fix_normals", "修复法线"))
self.buttons["fix_normals"].setObjectName("fixNormalsButton")
self.buttons["fix_normals"].setIcon(ui_utils.load_icon("repair_normals.png"))
self.buttons["fix_normals"].setMinimumHeight(30)
self.buttons["fix_normals"].setSizePolicy(size_policy)
self.buttons["fix_vertex_order"] = QtWidgets.QPushButton(LANG.get("fix_vertex_order", "修复点序"))
self.buttons["fix_vertex_order"] = QtWidgets.QPushButton(get_text("fix_vertex_order", "修复点序"))
self.buttons["fix_vertex_order"].setObjectName("fixVertexOrderButton")
self.buttons["fix_vertex_order"].setIcon(ui_utils.load_icon("repair_vertex_order.png"))
self.buttons["fix_vertex_order"].setMinimumHeight(30)
@@ -455,13 +465,13 @@ class GeometryUI(ui_utils.BaseUI):
self.layouts["buttons_row2_layout"].addWidget(self.buttons["fix_vertex_order"])
# 第三行按钮
self.buttons["fix_seams"] = QtWidgets.QPushButton(LANG.get("fix_seams", "修复接缝"))
self.buttons["fix_seams"] = QtWidgets.QPushButton(get_text("fix_seams", "修复接缝"))
self.buttons["fix_seams"].setObjectName("fixSeamsButton")
self.buttons["fix_seams"].setIcon(ui_utils.load_icon("polyChipOff.png"))
self.buttons["fix_seams"].setMinimumHeight(30)
self.buttons["fix_seams"].setSizePolicy(size_policy)
self.buttons["optimize_scene"] = QtWidgets.QPushButton(LANG.get("optimize_scene", "优化场景"))
self.buttons["optimize_scene"] = QtWidgets.QPushButton(get_text("optimize_scene", "优化场景"))
self.buttons["optimize_scene"].setObjectName("optimizeSceneButton")
self.buttons["optimize_scene"].setIcon(ui_utils.load_icon("singlePerspLayout.png"))
self.buttons["optimize_scene"].setMinimumHeight(30)
@@ -495,7 +505,7 @@ class GeometryUI(ui_utils.BaseUI):
# 将清理按钮直接添加到标签页控件的右上角
# 设置标签页控件的角落部件
self.controls["tab_widget"].setCornerWidget(self.buttons["clear"], QtCore.Qt.TopRightCorner)
self.controls["tab_widget"].setCornerWidget(self.buttons["clean"], QtCore.Qt.TopRightCorner)
# 添加标签页控件到主布局
self.layouts["main_layout"].addWidget(self.controls["tab_widget"])
@@ -553,7 +563,7 @@ class GeometryUI(ui_utils.BaseUI):
)
# 连接清理按钮
self.buttons["clear"].clicked.connect(utils_geometry.clean)
self.buttons["clean"].clicked.connect(utils_geometry.clean)
# 连接拓扑结构下拉框
self.controls["topology_combo"].currentIndexChanged.connect(utils_geometry.update_topology)

View File

@@ -3,10 +3,321 @@
"""
Localization module
本地化模块 - 提供UI文本的多语言支持
"""
import maya.cmds as cmds
import config
# 当前选择的语言
current_language = config.TOOL_LANG # 默认使用中文
# 语言字典
LANG = {
"en_US": {
# 主界面
"geometry": "Geometry",
"rigging": "Rigging",
"behaviour": "Behaviour",
"definition": "Definition",
# 几何体模块
"geometry_title": "Geometry",
"clean": " Clean ",
"load": "Load mesh",
"clear_all_models": " Clean all models",
"add_model": "Add Model",
"remove_model": "Remove Model",
"duplicate_model": "Duplicate Model",
"model_properties": "Model Properties",
"model_tools": "Model Tools",
"name": "Name",
"enter_model_name": "Enter Model Name",
"type": "Type",
"visible": "Visible",
"apply": "Apply",
"reset": "Reset",
"topology_structure": "Topology Structure",
"select_lod": "Select LOD",
"all": "All",
"create_lod": "Create LOD",
"separate_model": "Separate Model",
"generate_face_components": "Generate Face Components",
"fix_point_order": "Fix point order",
"fix_normals": "Fix normal",
"fix_vertex_order": "Fix vertex order",
"fix_seams": "Fix seams",
"standardize_naming": "Standardize Naming",
"optimize_scene": "Optimize Scene",
"auto_group": "Auto Group",
"create_custom_lod": "Create Custom LOD",
"import_lod": "Import LOD",
"export_lod": "Export LOD",
"search": "Search...",
# 绑定模块
"rigging_title": "Rigging System",
"presets": "Presets",
"assets": "Assets",
"descriptor": "Descriptor",
"project_path": "Project Path",
"Presets DNA:": "Presets DNA:",
"gender": "Gender",
"age": "Age",
"translation_unit": "Translation Unit",
"rotation_unit": "Rotation Unit",
"coordinate_system": "Coordinate System",
"lod_count": "LOD Count",
"archetype": "Archetype",
"import_skeleton": "Import Skeleton",
"build_rigging": "Build Rigging",
"remove_all": "Remove All",
# 行为模块
"behaviour_title": "Behaviour System",
"search": "Search...",
" Range - ": " Range - ",
" Range + ": " Range + ",
"Raw Control": "Raw Control",
"Related BlendShapes": "Related BlendShapes",
"Add": "Add",
"Delete": "Delete",
"Batch": "Batch",
"Rebuild": "Rebuild",
"Reposition": "Reposition",
"Blend": "Blend",
"Flip Target": "Flip Target",
"Mirror Target": "Mirror Target",
"Find Flip Target": "Find Flip Target",
"Add BlendShape": "Add BlendShape",
"Delete BlendShape": "Delete BlendShape",
"Batch BlendShape": "Batch BlendShape",
"Rebuild Select": "Rebuild Select",
"Reposition Joints": "Reposition Joints",
"Blend Select": "Blend Select",
"Reset Default": "Reset Default",
"Find Select": "Find Select",
"Write Current": "Write Current",
"Controller Find": "Controller Find",
"Select Joint": "Select Joint",
"Find Mirror": "Find Mirror",
# 定义模块
"definition_title": "Definition System",
"lods": "LODs",
"meshes": "Meshes",
"bones": "Bones",
"name_pattern": "Name Pattern",
"expression": "Expression",
"add_lod": "Add LOD",
"remove_lod": "Remove LOD",
"duplicate_lod": "Duplicate LOD",
"add_mesh": "Add Mesh",
"remove_mesh": "Remove Mesh",
"duplicate_mesh": "Duplicate Mesh",
"add_bone": "Add Bone",
"remove_bone": "Remove Bone",
"duplicate_bone": "Duplicate Bone",
"import_definition": "Import Definition",
"export_definition": "Export Definition",
# 工具栏
"Save DNA": "Save DNA",
"Open DNA": "Open DNA",
"Create RL4 node": "Create RL4 Node",
"Delete RL4 node": "Delete RL4 Node",
"Import skin": "Import Skin",
"Export skin": "Export Skin",
"Copy skin": "Copy Skin",
"Switch language": "Switch Language",
"Help": "Help"
},
"zh_CN": {
# 主界面
"geometry": "几何体",
"rigging": "绑定",
"behaviour": "行为",
"definition": "定义",
# 几何体模块
"geometry_title": "几何模型",
"clean": " 清 理 ",
"load": " 加 载 ",
"clear_all_models": " 清理所有模型",
"add_model": "添加模型",
"remove_model": "移除模型",
"duplicate_model": "复制模型",
"model_properties": "模型属性",
"model_tools": "模型工具",
"name": "名称",
"enter_model_name": "输入模型名称",
"type": "类型",
"visible": "可见",
"apply": "应用",
"reset": "重置",
"topology_structure": "拓扑结构",
"select_lod": "选择LOD",
"all": "全部",
"create_lod": "创建LOD",
"separate_model": "模型分离",
"generate_face_components": "生成面部配件",
"fix_point_order": "修复点序",
"fix_normals": "修复法线",
"fix_vertex_order": "修复点序",
"fix_seams": "修复接缝",
"standardize_naming": "标准化命名",
"optimize_scene": "优化场景",
"auto_group": "自动分组",
"create_custom_lod": "创建自定义LOD",
"import_lod": "导入LOD",
"export_lod": "导出LOD",
"search": "搜索...",
# 绑定模块
"rigging_title": "绑定系统",
"presets": "预设",
"assets": "资源",
"descriptor": "描述器",
"project_path": "项目路径",
"Presets DNA:": "预设 DNA:",
"gender": "性别",
"age": "年龄",
"translation_unit": "平移单位",
"rotation_unit": "旋转单位",
"coordinate_system": "坐标系统",
"lod_count": "LOD数量",
"archetype": "原型",
"import_skeleton": "导入骨骼",
"build_rigging": "创建绑定",
"remove_all": "移除全部",
# 行为模块
"behaviour_title": "行为系统",
"search": "搜索...",
" Range - ": " 范围 - ",
" Range + ": " 范围 + ",
"Raw Control": "原始控制",
"Related BlendShapes": "相关形态混合",
"Add": "添加",
"Delete": "删除",
"Batch": "批量",
"Rebuild": "重建",
"Reposition": "重定位",
"Blend": "混合",
"Flip Target": "翻转目标",
"Mirror Target": "镜像目标",
"Find Flip Target": "查找翻转目标",
"Add BlendShape": "添加形态混合",
"Delete BlendShape": "删除形态混合",
"Batch BlendShape": "批量形态混合",
"Rebuild Select": "重建选择",
"Reposition Joints": "重定位关节",
"Blend Select": "混合选择",
"Reset Default": "重置默认",
"Find Select": "查找选择",
"Write Current": "写入当前",
"Controller Find": "查找控制器",
"Select Joint": "选择关节",
"Find Mirror": "查找镜像",
# 定义模块
"definition_title": "定义系统",
"lods": "级别细节",
"meshes": "网格",
"bones": "骨骼",
"name_pattern": "命名模式",
"expression": "表情",
"add_lod": "添加LOD",
"remove_lod": "移除LOD",
"duplicate_lod": "复制LOD",
"add_mesh": "添加网格",
"remove_mesh": "移除网格",
"duplicate_mesh": "复制网格",
"add_bone": "添加骨骼",
"remove_bone": "移除骨骼",
"duplicate_bone": "复制骨骼",
"import_definition": "导入定义",
"export_definition": "导出定义",
# 工具栏
"保存DNA": "保存DNA",
"打开DNA": "打开DNA",
"创建RL4节点": "创建RL4节点",
"删除RL4节点": "删除RL4节点",
"导入蒙皮": "导入蒙皮",
"导出蒙皮": "导出蒙皮",
"复制蒙皮": "复制蒙皮",
"切换语言": "切换语言",
"帮助": "帮助"
}
}
def get_text(key, default=None):
"""
获取当前语言的文本
Args:
key: 文本键名
default: 如果找不到对应文本,返回的默认值
Returns:
str: 当前语言对应的文本
"""
global current_language
if not default:
default = key
# 如果key在当前语言中存在返回翻译文本
if key in LANG.get(current_language, {}):
return LANG[current_language][key]
# 如果只在英文中存在,返回英文
if key in LANG.get("en_US", {}):
return LANG["en_US"][key]
# 否则返回默认值
return default
def switch_language():
"""
切换当前语言
在中文和英文之间切换
Returns:
str: 切换后的语言代码
"""
global current_language
# 切换语言
if current_language == "zh_CN":
current_language = "en_US"
print("已切换到英文界面")
else:
current_language = "zh_CN"
print("已切换到中文界面")
return current_language
def get_current_language():
"""
获取当前语言代码
Returns:
str: 当前语言代码
"""
return current_language
def set_language(lang_code):
"""
设置当前语言
Args:
lang_code: 语言代码,如'zh_CN''en_US'
Returns:
bool: 是否设置成功
"""
global current_language
if lang_code in LANG:
current_language = lang_code
return True
return False

View File

@@ -55,6 +55,7 @@ TOOL_HEIGHT = config.TOOL_HEIGHT
#========================================= LOCATION =======================================
from scripts.ui import localization
LANG = localization.LANG
get_text = localization.get_text
class RiggingUI(ui_utils.BaseUI):
"""
@@ -93,7 +94,7 @@ class RiggingUI(ui_utils.BaseUI):
包括按钮、标签、列表等
"""
# 标题标签 - 使用HTML格式化标题
title_text = f"<h4 style='margin:0;padding:5px;'>{LANG.get('rigging_title', '骨骼绑定')}</h4>"
title_text = f"<h4 style='margin:0;padding:5px;'>{get_text('rigging_title', '骨骼绑定')}</h4>"
self.controls["title_label"] = QtWidgets.QLabel(title_text)
self.controls["title_label"].setObjectName("riggingTitleLabel")
self.controls["title_label"].setAlignment(QtCore.Qt.AlignCenter)
@@ -108,7 +109,7 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["presets_panel"].setObjectName("presetsPanel")
# Presets 组
self.controls["presets_group"] = QtWidgets.QGroupBox(LANG.get("presets", "Presets"))
self.controls["presets_group"] = QtWidgets.QGroupBox(get_text("presets", "Presets"))
self.controls["presets_group"].setObjectName("presetsGroup")
# 创建预设显示区域
@@ -189,14 +190,14 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["presets_slider"].setValue(50)
# 导出预设按钮
self.buttons["export_presets"] = QtWidgets.QPushButton(LANG.get("export_presets", "导出预设"))
self.buttons["export_presets"] = QtWidgets.QPushButton(get_text("export_presets", "导出预设"))
self.buttons["export_presets"].setObjectName("exportPresetsButton")
self.buttons["export_presets"].setIcon(QtGui.QIcon(os.path.join(ICONS_PATH, "export.png")))
self.buttons["export_presets"].setIcon(ui_utils.load_icon("export"))
# 导入预设按钮
self.buttons["import_presets"] = QtWidgets.QPushButton(LANG.get("import_presets", "导入预设"))
self.buttons["import_presets"] = QtWidgets.QPushButton(get_text("import_presets", "导入预设"))
self.buttons["import_presets"].setObjectName("importPresetsButton")
self.buttons["import_presets"].setIcon(QtGui.QIcon(os.path.join(ICONS_PATH, "import.png")))
self.buttons["import_presets"].setIcon(ui_utils.load_icon("import"))
# 添加到滑块布局
self.controls["presets_slider_layout"].addWidget(self.controls["presets_count_label"])
@@ -209,11 +210,11 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["assets_panel"].setObjectName("assetsPanel")
# Assets 组
self.controls["assets_group"] = QtWidgets.QGroupBox(LANG.get("assets", "Assets"))
self.controls["assets_group"] = QtWidgets.QGroupBox(get_text("assets", "Assets"))
self.controls["assets_group"].setObjectName("assetsGroup")
# 项目路径标签和输入框
self.controls["project_path_label"] = QtWidgets.QLabel(LANG.get("project_path", "项目路径:"))
self.controls["project_path_label"] = QtWidgets.QLabel(get_text("project_path", "项目路径:"))
self.controls["project_path_label"].setObjectName("projectPathLabel")
self.controls["project_path_input"] = QtWidgets.QLineEdit()
@@ -221,39 +222,43 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["project_path_input"].setText("D:/Personal/Document/maya/SuperRiggingEditor/files/data/MetaHuman")
# 浏览按钮
self.buttons["browse_path"] = QtWidgets.QPushButton("...")
self.buttons["browse_path"] = QtWidgets.QPushButton(" . . . ")
self.buttons["browse_path"].setObjectName("browsePathButton")
self.buttons["browse_path"].setFixedWidth(30)
self.buttons["browse_path"].setIcon(ui_utils.load_icon("import"))
self.buttons["browse_path"].setMinimumWidth(0)
self.buttons["browse_path"].setFixedWidth(100)
# Presets DNA 标签和输入框
self.controls["presets_dna_label"] = QtWidgets.QLabel(LANG.get("presets_dna", "Presets DNA:"))
self.controls["presets_dna_label"] = QtWidgets.QLabel(get_text("Presets DNA:", "预设 DNA:"))
self.controls["presets_dna_label"].setObjectName("presetsDnaLabel")
self.controls["presets_dna_input"] = QtWidgets.QLineEdit()
self.controls["presets_dna_input"].setObjectName("presetsDnaInput")
# 浏览按钮
self.buttons["browse_dna"] = QtWidgets.QPushButton("...")
self.buttons["browse_dna"] = QtWidgets.QPushButton(" . . . ")
self.buttons["browse_dna"].setObjectName("browseDnaButton")
self.buttons["browse_dna"].setFixedWidth(30)
self.buttons["browse_dna"].setIcon(ui_utils.load_icon("import"))
self.buttons["browse_dna"].setMinimumWidth(0)
self.buttons["browse_dna"].setFixedWidth(100)
# 3. Descriptor 面板
self.controls["descriptor_panel"] = QtWidgets.QWidget()
self.controls["descriptor_panel"].setObjectName("descriptorPanel")
# Descriptor 组
self.controls["descriptor_group"] = QtWidgets.QGroupBox(LANG.get("descriptor", "Descriptor"))
self.controls["descriptor_group"] = QtWidgets.QGroupBox(get_text("descriptor", "Descriptor"))
self.controls["descriptor_group"].setObjectName("descriptorGroup")
# 名称标签和输入框
self.controls["name_label"] = QtWidgets.QLabel(LANG.get("name", "名称:"))
self.controls["name_label"] = QtWidgets.QLabel(get_text("name", "名称:"))
self.controls["name_label"].setObjectName("nameLabel")
self.controls["name_input"] = QtWidgets.QLineEdit()
self.controls["name_input"].setObjectName("nameInput")
# 原型标签和下拉框
self.controls["archetype_label"] = QtWidgets.QLabel(LANG.get("archetype", "原型:"))
self.controls["archetype_label"] = QtWidgets.QLabel(get_text("archetype", "原型:"))
self.controls["archetype_label"].setObjectName("archetypeLabel")
self.controls["archetype_combo"] = QtWidgets.QComboBox()
@@ -263,7 +268,7 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["archetype_combo"].addItem("african")
# 性别标签和下拉框
self.controls["gender_label"] = QtWidgets.QLabel(LANG.get("gender", "性别:"))
self.controls["gender_label"] = QtWidgets.QLabel(get_text("gender", "性别:"))
self.controls["gender_label"].setObjectName("genderLabel")
self.controls["gender_combo"] = QtWidgets.QComboBox()
@@ -272,17 +277,17 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["gender_combo"].addItem("male")
# 年龄标签和输入框
self.controls["age_label"] = QtWidgets.QLabel(LANG.get("age", "年龄:"))
self.controls["age_label"] = QtWidgets.QLabel(get_text("age", "年龄:"))
self.controls["age_label"].setObjectName("ageLabel")
self.controls["age_spinner"] = QtWidgets.QSpinBox()
self.controls["age_spinner"].setObjectName("ageSpinner")
self.controls["age_spinner"].setMinimum(1)
self.controls["age_spinner"].setMaximum(100)
self.controls["age_spinner"].setValue(18)
self.controls["age_spinner"].setValue(24)
# 平移单位标签和下拉框
self.controls["translation_unit_label"] = QtWidgets.QLabel(LANG.get("translation_unit", "平移单位:"))
self.controls["translation_unit_label"] = QtWidgets.QLabel(get_text("translation_unit", "平移单位:"))
self.controls["translation_unit_label"].setObjectName("translationUnitLabel")
self.controls["translation_unit_combo"] = QtWidgets.QComboBox()
@@ -292,7 +297,7 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["translation_unit_combo"].addItem("m")
# 旋转单位标签和下拉框
self.controls["rotation_unit_label"] = QtWidgets.QLabel(LANG.get("rotation_unit", "旋转单位:"))
self.controls["rotation_unit_label"] = QtWidgets.QLabel(get_text("rotation_unit", "旋转单位:"))
self.controls["rotation_unit_label"].setObjectName("rotationUnitLabel")
self.controls["rotation_unit_combo"] = QtWidgets.QComboBox()
@@ -301,7 +306,7 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["rotation_unit_combo"].addItem("radians")
# 坐标系统标签和下拉框
self.controls["coordinate_system_label"] = QtWidgets.QLabel(LANG.get("coordinate_system", "坐标系统:"))
self.controls["coordinate_system_label"] = QtWidgets.QLabel(get_text("coordinate_system", "坐标系统:"))
self.controls["coordinate_system_label"].setObjectName("coordinateSystemLabel")
self.controls["coordinate_system_combo"] = QtWidgets.QComboBox()
@@ -310,7 +315,7 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["coordinate_system_combo"].addItem("ZAxisUp")
# LOD数量标签和输入框
self.controls["lod_count_label"] = QtWidgets.QLabel(LANG.get("lod_count", "LOD数量:"))
self.controls["lod_count_label"] = QtWidgets.QLabel(get_text("lod_count", "LOD数量:"))
self.controls["lod_count_label"].setObjectName("lodCountLabel")
self.controls["lod_count_spinner"] = QtWidgets.QSpinBox()
@@ -324,193 +329,19 @@ class RiggingUI(ui_utils.BaseUI):
self.controls["bottom_buttons_panel"].setObjectName("bottomButtonsPanel")
# 删除所有按钮
self.buttons["remove_all"] = QtWidgets.QPushButton(LANG.get("remove_all", "删除所有"))
self.buttons["remove_all"] = QtWidgets.QPushButton(get_text("remove_all", "删除所有"))
self.buttons["remove_all"].setObjectName("removeAllButton")
self.buttons["remove_all"].setIcon(QtGui.QIcon(os.path.join(ICONS_PATH, "delete.png")))
self.buttons["remove_all"].setIcon(ui_utils.load_icon("delete.png"))
# 导入骨骼按钮
self.buttons["import_skeleton"] = QtWidgets.QPushButton(LANG.get("import_skeleton", "导入骨骼"))
self.buttons["import_skeleton"] = QtWidgets.QPushButton(get_text("import_skeleton", "导入骨骼"))
self.buttons["import_skeleton"].setObjectName("importSkeletonButton")
self.buttons["import_skeleton"].setIcon(QtGui.QIcon(os.path.join(ICONS_PATH, "import_skeleton.png")))
self.buttons["import_skeleton"].setIcon(ui_utils.load_icon("HIKCharacterToolSkeleton.png"))
# 生成绑定按钮
self.buttons["build_rigging"] = QtWidgets.QPushButton(LANG.get("build_rigging", "生成绑定"))
self.buttons["build_rigging"] = QtWidgets.QPushButton(get_text("build_rigging", "生成绑定"))
self.buttons["build_rigging"].setObjectName("buildRiggingButton")
self.buttons["build_rigging"].setIcon(QtGui.QIcon(os.path.join(ICONS_PATH, "build_rigging.png")))
# 左侧面板控件 - 控制器列表
self.controls["controller_group"] = QtWidgets.QGroupBox("Controllers [000]")
self.controls["controller_group"].setObjectName("controllerGroup")
# 控制器列表
self.controls["controller_list"] = QtWidgets.QListWidget()
self.controls["controller_list"].setObjectName("controllerList")
# 添加测试项目
for i in range(3):
item = QtWidgets.QListWidgetItem(f"Controller_{i}")
self.controls["controller_list"].addItem(item)
# 控制器操作按钮
self.buttons["add_controller"] = QtWidgets.QPushButton(LANG.get("add_controller", "添加控制器"))
self.buttons["add_controller"].setObjectName("addControllerButton")
self.buttons["remove_controller"] = QtWidgets.QPushButton(LANG.get("remove_controller", "移除控制器"))
self.buttons["remove_controller"].setObjectName("removeControllerButton")
self.buttons["duplicate_controller"] = QtWidgets.QPushButton(LANG.get("duplicate_controller", "复制控制器"))
self.buttons["duplicate_controller"].setObjectName("duplicateControllerButton")
# 右侧面板控件 - 关节属性
self.controls["joint_properties_group"] = QtWidgets.QGroupBox(LANG.get("joint_properties", "关节属性"))
self.controls["joint_properties_group"].setObjectName("jointPropertiesGroup")
# 关节名称标签和输入框
self.controls["joint_name_label"] = QtWidgets.QLabel(LANG.get("name", "名称:"))
self.controls["joint_name_label"].setObjectName("jointNameLabel")
self.controls["joint_name_input"] = QtWidgets.QLineEdit()
self.controls["joint_name_input"].setObjectName("jointNameInput")
self.controls["joint_name_input"].setPlaceholderText(LANG.get("enter_joint_name", "输入关节名称"))
# 关节位置标签和输入框
self.controls["joint_position_label"] = QtWidgets.QLabel(LANG.get("position", "位置:"))
self.controls["joint_position_label"].setObjectName("jointPositionLabel")
# X坐标
self.controls["joint_x_label"] = QtWidgets.QLabel("X:")
self.controls["joint_x_label"].setObjectName("jointXLabel")
self.controls["joint_x_input"] = QtWidgets.QLineEdit("0.0")
self.controls["joint_x_input"].setObjectName("jointXInput")
# Y坐标
self.controls["joint_y_label"] = QtWidgets.QLabel("Y:")
self.controls["joint_y_label"].setObjectName("jointYLabel")
self.controls["joint_y_input"] = QtWidgets.QLineEdit("0.0")
self.controls["joint_y_input"].setObjectName("jointYInput")
# Z坐标
self.controls["joint_z_label"] = QtWidgets.QLabel("Z:")
self.controls["joint_z_label"].setObjectName("jointZLabel")
self.controls["joint_z_input"] = QtWidgets.QLineEdit("0.0")
self.controls["joint_z_input"].setObjectName("jointZInput")
# 关节旋转标签和输入框
self.controls["joint_rotation_label"] = QtWidgets.QLabel(LANG.get("rotation", "旋转:"))
self.controls["joint_rotation_label"].setObjectName("jointRotationLabel")
# X旋转
self.controls["joint_rx_label"] = QtWidgets.QLabel("X:")
self.controls["joint_rx_label"].setObjectName("jointRXLabel")
self.controls["joint_rx_input"] = QtWidgets.QLineEdit("0.0")
self.controls["joint_rx_input"].setObjectName("jointRXInput")
# Y旋转
self.controls["joint_ry_label"] = QtWidgets.QLabel("Y:")
self.controls["joint_ry_label"].setObjectName("jointRYLabel")
self.controls["joint_ry_input"] = QtWidgets.QLineEdit("0.0")
self.controls["joint_ry_input"].setObjectName("jointRYInput")
# Z旋转
self.controls["joint_rz_label"] = QtWidgets.QLabel("Z:")
self.controls["joint_rz_label"].setObjectName("jointRZLabel")
self.controls["joint_rz_input"] = QtWidgets.QLineEdit("0.0")
self.controls["joint_rz_input"].setObjectName("jointRZInput")
# 关节缩放标签和输入框
self.controls["joint_scale_label"] = QtWidgets.QLabel(LANG.get("scale", "缩放:"))
self.controls["joint_scale_label"].setObjectName("jointScaleLabel")
# X缩放
self.controls["joint_sx_label"] = QtWidgets.QLabel("X:")
self.controls["joint_sx_label"].setObjectName("jointSXLabel")
self.controls["joint_sx_input"] = QtWidgets.QLineEdit("1.0")
self.controls["joint_sx_input"].setObjectName("jointSXInput")
# Y缩放
self.controls["joint_sy_label"] = QtWidgets.QLabel("Y:")
self.controls["joint_sy_label"].setObjectName("jointSYLabel")
self.controls["joint_sy_input"] = QtWidgets.QLineEdit("1.0")
self.controls["joint_sy_input"].setObjectName("jointSYInput")
# Z缩放
self.controls["joint_sz_label"] = QtWidgets.QLabel("Z:")
self.controls["joint_sz_label"].setObjectName("jointSZLabel")
self.controls["joint_sz_input"] = QtWidgets.QLineEdit("1.0")
self.controls["joint_sz_input"].setObjectName("jointSZInput")
# 关节属性按钮
self.buttons["apply_joint_properties"] = QtWidgets.QPushButton(LANG.get("apply", "应用"))
self.buttons["apply_joint_properties"].setObjectName("applyJointPropertiesButton")
self.buttons["reset_joint_properties"] = QtWidgets.QPushButton(LANG.get("reset", "重置"))
self.buttons["reset_joint_properties"].setObjectName("resetJointPropertiesButton")
# 右侧面板控件 - 绑定工具
self.controls["binding_tools_group"] = QtWidgets.QGroupBox(LANG.get("binding_tools", "绑定工具"))
self.controls["binding_tools_group"].setObjectName("bindingToolsGroup")
# 绑定工具按钮
self.buttons["create_binding"] = QtWidgets.QPushButton(LANG.get("create_binding", "创建绑定"))
self.buttons["create_binding"].setObjectName("createBindingButton")
self.buttons["copy_skin"] = QtWidgets.QPushButton(LANG.get("copy_skin", "复制蒙皮"))
self.buttons["copy_skin"].setObjectName("copySkinButton")
self.buttons["mirror_skin"] = QtWidgets.QPushButton(LANG.get("mirror_skin", "镜像蒙皮"))
self.buttons["mirror_skin"].setObjectName("mirrorSkinButton")
self.buttons["paint_weights"] = QtWidgets.QPushButton(LANG.get("paint_weights", "绘制权重"))
self.buttons["paint_weights"].setObjectName("paintWeightsButton")
# 底部工具面板
# DNA部分
self.controls["dna_group"] = QtWidgets.QGroupBox(LANG.get("dna", "DNA"))
self.controls["dna_group"].setObjectName("dnaGroup")
self.buttons["import_dna"] = QtWidgets.QPushButton(LANG.get("import_dna", "导入DNA"))
self.buttons["import_dna"].setObjectName("importDnaButton")
self.buttons["export_dna"] = QtWidgets.QPushButton(LANG.get("export_dna", "导出DNA"))
self.buttons["export_dna"].setObjectName("exportDnaButton")
self.buttons["calibrate_dna"] = QtWidgets.QPushButton(LANG.get("calibrate_dna", "校准DNA"))
self.buttons["calibrate_dna"].setObjectName("calibrateDnaButton")
# 骨骼部分
self.controls["skeleton_tools_group"] = QtWidgets.QGroupBox(LANG.get("skeleton_tools", "骨骼工具"))
self.controls["skeleton_tools_group"].setObjectName("skeletonToolsGroup")
self.buttons["import_skeleton"] = QtWidgets.QPushButton(LANG.get("import_skeleton", "导入骨骼"))
self.buttons["import_skeleton"].setObjectName("importSkeletonButton")
self.buttons["export_skeleton"] = QtWidgets.QPushButton(LANG.get("export_skeleton", "导出骨骼"))
self.buttons["export_skeleton"].setObjectName("exportSkeletonButton")
self.buttons["calibrate_skeleton"] = QtWidgets.QPushButton(LANG.get("calibrate_skeleton", "校准骨骼"))
self.buttons["calibrate_skeleton"].setObjectName("calibrateSkeletonButton")
# 工具部分
self.controls["rigging_tools_group"] = QtWidgets.QGroupBox(LANG.get("rigging_tools", "绑定工具"))
self.controls["rigging_tools_group"].setObjectName("riggingToolsGroup")
self.buttons["generate_controllers"] = QtWidgets.QPushButton(LANG.get("generate_controllers", "生成控制器"))
self.buttons["generate_controllers"].setObjectName("generateControllersButton")
self.buttons["generate_body"] = QtWidgets.QPushButton(LANG.get("generate_body", "生成身体"))
self.buttons["generate_body"].setObjectName("generateBodyButton")
self.buttons["clean_rigging"] = QtWidgets.QPushButton(LANG.get("clean_rigging", "清理绑定"))
self.buttons["clean_rigging"].setObjectName("cleanRiggingButton")
self.buttons["build_rigging"].setIcon(ui_utils.load_icon("HIKcreateControlRig.png"))
#========================================= LAYOUT =======================================
def create_layouts(self):
@@ -576,42 +407,61 @@ class RiggingUI(ui_utils.BaseUI):
descriptor_layout.setContentsMargins(0, 0, 0, 0)
descriptor_layout.setSpacing(5)
# Descriptor 组布局
# Descriptor 组布局优化紧凑表单式参考主流DCC风格
descriptor_group_layout = QtWidgets.QGridLayout(self.controls["descriptor_group"])
descriptor_group_layout.setContentsMargins(5, 5, 5, 5)
descriptor_group_layout.setSpacing(5)
descriptor_group_layout.setContentsMargins(16, 8, 16, 8) # 左右加大
descriptor_group_layout.setHorizontalSpacing(48) # 增大左右间距
descriptor_group_layout.setVerticalSpacing(10)
# 添加名称标签和输入框
# 统一控件高度和字体
for key in [
"name_input", "archetype_combo", "gender_combo", "age_spinner", "lod_count_spinner",
"translation_unit_combo", "rotation_unit_combo", "coordinate_system_combo"
]:
self.controls[key].setFixedHeight(24)
self.controls[key].setStyleSheet("font-size: 13px;")
for key in [
"name_label", "archetype_label", "gender_label", "age_label", "lod_count_label",
"translation_unit_label", "rotation_unit_label", "coordinate_system_label"
]:
self.controls[key].setStyleSheet("font-size: 13px;")
self.controls[key].setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
# 设置每列最小宽度/拉伸,保证左右均衡
descriptor_group_layout.setColumnMinimumWidth(0, 80) # 左标签
descriptor_group_layout.setColumnMinimumWidth(1, 180) # 左输入
descriptor_group_layout.setColumnMinimumWidth(2, 80) # 右标签
descriptor_group_layout.setColumnMinimumWidth(3, 180) # 右输入
descriptor_group_layout.setColumnStretch(1, 2)
descriptor_group_layout.setColumnStretch(3, 2)
# 第一行:名称
descriptor_group_layout.addWidget(self.controls["name_label"], 0, 0)
descriptor_group_layout.addWidget(self.controls["name_input"], 0, 1, 1, 3)
# 添加原型标签和下拉框
# 第二行:原型、性别
descriptor_group_layout.addWidget(self.controls["archetype_label"], 1, 0)
descriptor_group_layout.addWidget(self.controls["archetype_combo"], 1, 1)
# 添加性别标签和下拉框
descriptor_group_layout.addWidget(self.controls["gender_label"], 1, 2)
descriptor_group_layout.addWidget(self.controls["gender_combo"], 1, 3)
# 添加年龄标签和输入框
# 第三行年龄、LOD数量
descriptor_group_layout.addWidget(self.controls["age_label"], 2, 0)
descriptor_group_layout.addWidget(self.controls["age_spinner"], 2, 1)
descriptor_group_layout.addWidget(self.controls["lod_count_label"], 2, 2)
descriptor_group_layout.addWidget(self.controls["lod_count_spinner"], 2, 3)
# 添加平移单位标签和下拉框
# 第四行:平移单位、旋转单位
descriptor_group_layout.addWidget(self.controls["translation_unit_label"], 3, 0)
descriptor_group_layout.addWidget(self.controls["translation_unit_combo"], 3, 1)
# 添加旋转单位标签和下拉框
descriptor_group_layout.addWidget(self.controls["rotation_unit_label"], 3, 2)
descriptor_group_layout.addWidget(self.controls["rotation_unit_combo"], 3, 3)
# 添加坐标系统标签和下拉框
# 第五行:坐标系统
descriptor_group_layout.addWidget(self.controls["coordinate_system_label"], 4, 0)
descriptor_group_layout.addWidget(self.controls["coordinate_system_combo"], 4, 1)
# 添加LOD数量标签和输入框
descriptor_group_layout.addWidget(self.controls["lod_count_label"], 4, 2)
descriptor_group_layout.addWidget(self.controls["lod_count_spinner"], 4, 3)
# 右侧空白占位,保持对齐美观
descriptor_group_layout.addItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum), 4, 2, 1, 2)
# 添加Descriptor组到Descriptor面板
descriptor_layout.addWidget(self.controls["descriptor_group"])
@@ -626,11 +476,12 @@ class RiggingUI(ui_utils.BaseUI):
bottom_buttons_layout.setContentsMargins(5, 5, 5, 5)
bottom_buttons_layout.setSpacing(10)
# 添加底部按钮
bottom_buttons_layout.addWidget(self.buttons["remove_all"])
bottom_buttons_layout.addStretch(1) # 添加弹性空间
bottom_buttons_layout.addWidget(self.buttons["import_skeleton"])
bottom_buttons_layout.addWidget(self.buttons["build_rigging"])
# 添加底部按钮(均等宽度)
for btn in [self.buttons["remove_all"], self.buttons["import_skeleton"], self.buttons["build_rigging"]]:
btn.setMinimumWidth(0)
btn.setMaximumWidth(16777215)
btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
bottom_buttons_layout.addWidget(btn)
# 添加底部按钮区域到主布局
main_layout.addWidget(self.controls["bottom_buttons_panel"])
@@ -645,35 +496,34 @@ class RiggingUI(ui_utils.BaseUI):
#======================================= FUNCTIONS ======================================
def create_connections(self):
"""
创建信号连接
创建信号连接设置UI控件的交互行为风格参考definition.py
"""
# 创建信号映射字典
signal_mapping = {
'buttons': {
# 绑定所有功能按钮
'add_joint_btn': {'signal': 'clicked', 'handler': utils_rigging.add_joint},
'remove_joint_btn': {'signal': 'clicked', 'handler': utils_rigging.remove_joint},
'duplicate_joint_btn': {'signal': 'clicked', 'handler': utils_rigging.duplicate_joint},
'add_controller_btn': {'signal': 'clicked', 'handler': utils_rigging.add_controller},
'remove_controller_btn': {'signal': 'clicked', 'handler': utils_rigging.remove_controller},
'duplicate_controller_btn': {'signal': 'clicked', 'handler': utils_rigging.duplicate_controller},
'import_dna_btn': {'signal': 'clicked', 'handler': utils_rigging.import_dna},
'export_dna_btn': {'signal': 'clicked', 'handler': utils_rigging.export_dna},
'calibrate_dna_btn': {'signal': 'clicked', 'handler': utils_rigging.calibrate_dna},
# 主要功能按钮直接连接
if "add_joint_btn" in self.buttons:
self.buttons["add_joint_btn"].clicked.connect(utils_rigging.add_joint)
if "remove_joint_btn" in self.buttons:
self.buttons["remove_joint_btn"].clicked.connect(utils_rigging.remove_joint)
if "duplicate_joint_btn" in self.buttons:
self.buttons["duplicate_joint_btn"].clicked.connect(utils_rigging.duplicate_joint)
if "add_controller_btn" in self.buttons:
self.buttons["add_controller_btn"].clicked.connect(utils_rigging.add_controller)
if "remove_controller_btn" in self.buttons:
self.buttons["remove_controller_btn"].clicked.connect(utils_rigging.remove_controller)
if "duplicate_controller_btn" in self.buttons:
self.buttons["duplicate_controller_btn"].clicked.connect(utils_rigging.duplicate_controller)
if "import_dna_btn" in self.buttons:
self.buttons["import_dna_btn"].clicked.connect(utils_rigging.import_dna)
if "export_dna_btn" in self.buttons:
self.buttons["export_dna_btn"].clicked.connect(utils_rigging.export_dna)
if "calibrate_dna_btn" in self.buttons:
self.buttons["calibrate_dna_btn"].clicked.connect(utils_rigging.calibrate_dna)
# 已创建按钮
'browse_path': {'signal': 'clicked', 'handler': lambda: utils_rigging.browse_file(self, "项目路径", self.controls["project_path_input"])},
'browse_dna': {'signal': 'clicked', 'handler': lambda: utils_rigging.browse_file(self, "DNA文件", self.controls["presets_dna_input"], "dna")},
'export_presets': {'signal': 'clicked', 'handler': utils_rigging.export_presets},
'import_presets': {'signal': 'clicked', 'handler': utils_rigging.import_presets},
},
'splitters': {
'main_splitter': {'signal': 'splitterMoved', 'handler': lambda pos, index: ui_utils.on_splitter_moved(self, pos, index)},
}
}
# 使用ui_utils中的通用函数连接信号
ui_utils.connect_ui_signals(self, signal_mapping)
# 连接Maya选择变化事件
ui_utils.connect_maya_selection_changed(self, lambda: utils_rigging.on_selection_changed(self), self.main_widget)
# 其它已创建按钮
if "browse_path" in self.buttons:
self.buttons["browse_path"].clicked.connect(lambda: utils_rigging.browse_file(self, "项目路径", self.controls["project_path_input"]))
if "browse_dna" in self.buttons:
self.buttons["browse_dna"].clicked.connect(lambda: utils_rigging.browse_file(self, "DNA文件", self.controls["presets_dna_input"], "dna"))
if "export_presets" in self.buttons:
self.buttons["export_presets"].clicked.connect(utils_rigging.export_presets)
if "import_presets" in self.buttons:
self.buttons["import_presets"].clicked.connect(utils_rigging.import_presets)

View File

@@ -1,5 +1,5 @@
/* 插件样式表 */
/* 作者: CGNICO */
/* 作者: Virtuos Games */
/* 版本: Alpha v1.0.0 */
/* ==================== 全局样式 ==================== */

View File

@@ -28,6 +28,7 @@ import sys
import os
from scripts.ui import ui_utils
from scripts.utils import utils_toolbar
from scripts.ui.localization import get_text
#========================================== CONFIG ========================================
import config
TOOL_NAME = config.TOOL_NAME
@@ -60,6 +61,19 @@ class ToolbarUI(ui_utils.BaseUI):
工具栏UI类 - 负责显示工具栏界面和基础操作
继承自BaseUI类实现工具栏相关的UI功能
"""
# 类变量,存储单例实例
_instance = None
@classmethod
def get_instance(cls):
"""
获取ToolbarUI的单例实例
Returns:
ToolbarUI: 单例实例如果不存在则返回None
"""
return cls._instance
#========================================== INIT ========================================
def __init__(self):
"""
@@ -68,6 +82,9 @@ class ToolbarUI(ui_utils.BaseUI):
"""
super(ToolbarUI, self).__init__()
# 设置单例实例
ToolbarUI._instance = self
# 创建主控件
self.main_widget = QtWidgets.QWidget()
self.main_widget.setObjectName("toolbarMainWidget")
@@ -103,6 +120,9 @@ class ToolbarUI(ui_utils.BaseUI):
self.buttons["Export skin"] = self._create_tool_button("导出蒙皮", "export_skin.png")
self.buttons["Copy skin"] = self._create_tool_button("复制蒙皮", "copy_skin.png")
# 翻译按钮
self.buttons["Translate"] = self._create_tool_button("切换语言", "translate.png")
# 帮助按钮
self.buttons["Help"] = self._create_tool_button("帮助", "help.png")
@@ -115,7 +135,7 @@ class ToolbarUI(ui_utils.BaseUI):
checkable: 是否可选中
"""
button = QtWidgets.QPushButton()
button.setToolTip(LANG.get(tooltip, tooltip))
button.setToolTip(get_text(tooltip, tooltip))
# 构建图标完整路径
icon_path = os.path.join(ICONS_PATH, icon_name)
@@ -126,7 +146,7 @@ class ToolbarUI(ui_utils.BaseUI):
button.setIconSize(QtCore.QSize(24, 24))
else:
# 如果图标不存在,使用文字
button.setText(LANG.get(tooltip, tooltip))
button.setText(get_text(tooltip, tooltip))
button.setObjectName(f"{tooltip.replace(' ', '_').lower()}_button")
button.setFixedSize(32, 32)
@@ -165,6 +185,8 @@ class ToolbarUI(ui_utils.BaseUI):
self.layouts["toolbar"].addWidget(self.buttons["Export skin"])
self.layouts["toolbar"].addWidget(self.buttons["Copy skin"])
self.layouts["toolbar"].addWidget(self._create_separator())
self.layouts["toolbar"].addWidget(self.buttons["Translate"])
self.layouts["toolbar"].addWidget(self._create_separator())
self.layouts["toolbar"].addWidget(self.buttons["Help"])
self.layouts["toolbar"].addStretch()
@@ -190,6 +212,9 @@ class ToolbarUI(ui_utils.BaseUI):
self.buttons["Export skin"].clicked.connect(utils_toolbar.export_skin)
self.buttons["Copy skin"].clicked.connect(utils_toolbar.copy_skin)
# 翻译按钮连接
self.buttons["Translate"].clicked.connect(utils_toolbar.toggle_language)
# 帮助按钮连接
self.buttons["Help"].clicked.connect(utils_toolbar.show_help)

View File

@@ -80,14 +80,12 @@ class BaseUI(object):
"""连接UI信号和槽"""
pass
def showEvent(self, event):
"""显示事件处理函数"""
# 调用父类的showEvent方法
super(BaseUI, self).showEvent(event)
# 强制设置分割器均等大小
if hasattr(self, 'splitters') and 'main_splitter' in self.splitters:
setup_splitter(self, 'main_splitter', equal_sizes=True)
def update_language(self):
"""
更新所有UI文本到当前语言
"""
if self.main_widget:
update_ui_texts(self.main_widget)
#============================================ UI HELPERS ==========================================
def connect_ui_signals(ui_instance, signal_mapping):
@@ -222,247 +220,78 @@ def load_icon(icon_name):
return QtGui.QIcon()
#============================================ SPLITTER ==========================================
def create_splitter(orientation=QtCore.Qt.Horizontal, parent=None):
def set_splitter_proportions(splitter, proportions):
"""
创建一个QSplitter对象并设置基本属性
设置分割器各部分的比例
Args:
orientation: 分割器方向,默认水平
parent: 父窗口对象
Returns:
QtWidgets.QSplitter: 创建的分割器对象
splitter: 要设置的分割器
proportions: 比例列表,如 [0.3, 0.7] 表示左侧占30%右侧占70%
"""
splitter = QtWidgets.QSplitter(orientation, parent)
splitter.setOpaqueResize(True) # 拖动时实时显示
splitter.setChildrenCollapsible(False) # 防止子部件被完全折叠
splitter.setHandleWidth(2) # 设置分割条宽度
return splitter
def setup_splitter(ui_instance, splitter_name="main_splitter", equal_sizes=True):
"""
设置分割器的属性和大小,确保完全自由调整
Args:
ui_instance: UI实例对象
splitter_name (str): 分割器名称
equal_sizes (bool): 是否设置均等大小
Returns:
bool: 是否成功设置
"""
# 检查分割器是否存在
if not hasattr(ui_instance, 'splitters') or splitter_name not in ui_instance.splitters:
print(f"分割器 '{splitter_name}' 不存在")
return False
# 获取分割器
splitter = ui_instance.splitters[splitter_name]
if not isinstance(splitter, QtWidgets.QSplitter):
print(f"对象 '{splitter_name}' 不是QSplitter类型")
return False
# 设置分割器属性
splitter.setOpaqueResize(True) # 拖动时实时显示
splitter.setChildrenCollapsible(False) # 防止子部件被完全折叠
splitter.setHandleWidth(2) # 设置分割条宽度
# 设置子部件属性
for i in range(splitter.count()):
# 设置伸缩因子 - 所有子部件使用相同的伸缩因子
splitter.setStretchFactor(i, 1)
# 设置子部件属性
widget = splitter.widget(i)
if widget:
# 确保子部件可以自由调整大小
widget.setMinimumWidth(10) # 设置最小宽度而不是0防止完全消失
widget.setMinimumHeight(10) # 设置最小高度而不是0防止完全消失
widget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# 设置均等大小
if equal_sizes and splitter.count() > 0:
# 计算每个部件应该的大小
total_size = splitter.width() if splitter.orientation() == QtCore.Qt.Horizontal else splitter.height()
size_per_widget = max(1, total_size // splitter.count())
sizes = [size_per_widget] * splitter.count()
# 立即设置大小
splitter.setSizes(sizes)
splitter.update()
# 延迟设置确保生效 - 使用两次定时器调用确保在UI完全加载后生效
QtCore.QTimer.singleShot(50, lambda: splitter.setSizes(sizes))
QtCore.QTimer.singleShot(200, lambda: splitter.setSizes(sizes))
# 连接分割器移动信号
splitter.splitterMoved.connect(lambda: on_splitter_moved(splitter))
return True
def on_splitter_moved(splitter):
"""
处理分割器移动事件
Args:
splitter: 被移动的分割器对象
"""
# 这里可以添加分割器移动后的处理逻辑
# 例如记录分割器位置或者调整其他UI元素
pass
def reset_splitter_sizes(splitter, equal_sizes=True):
"""
重置分割器大小,可选择设置均等大小
Args:
splitter: 分割器对象
equal_sizes: 是否设置均等大小
"""
if not isinstance(splitter, QtWidgets.QSplitter):
if not isinstance(splitter, QtWidgets.QSplitter) or not proportions:
return
if equal_sizes and splitter.count() > 0:
# 计算每个部件应该的大小
total_size = splitter.width() if splitter.orientation() == QtCore.Qt.Horizontal else splitter.height()
size_per_widget = max(1, total_size // splitter.count())
sizes = [size_per_widget] * splitter.count()
# 设置大小
splitter.setSizes(sizes)
splitter.update()
# 延迟设置确保生效
QtCore.QTimer.singleShot(50, lambda: splitter.setSizes(sizes))
QtCore.QTimer.singleShot(200, lambda: splitter.setSizes(sizes))
def force_equal_splitter_sizes(ui_instance, splitter_name="main_splitter"):
"""
强制设置分割器大小为均等,并确保完全自由调整
Args:
ui_instance: UI实例对象
splitter_name (str): 分割器名称
"""
# 调用通用的分割器设置函数,设置均等大小
return setup_splitter(ui_instance, splitter_name, equal_sizes=True)
def set_splitter_children_minimum_size(ui_instance, splitter_name="main_splitter", recursive=True):
"""
设置分割器所有子元素的最小宽度和高度为0允许完全自由调整
Args:
ui_instance: UI实例对象
splitter_name (str): 分割器名称
recursive (bool): 是否递归设置所有子元素
"""
# 检查分割器是否存在
if not hasattr(ui_instance, 'splitters') or splitter_name not in ui_instance.splitters:
# 确保比例数量与分割器子部件数量一致
if len(proportions) != splitter.count():
return
# 获取分割器
splitter = ui_instance.splitters[splitter_name]
# 确保比例总和为1
total = sum(proportions)
if total <= 0:
return
# 设置分割器属性
splitter.setOpaqueResize(True)
splitter.setChildrenCollapsible(False)
# 设置所有子部件的最小尺寸为0
for i in range(splitter.count()):
widget = splitter.widget(i)
if widget and recursive:
# 设置最小尺寸为0
widget.setMinimumWidth(0)
widget.setMinimumHeight(0)
widget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# 递归设置子控件
_set_widget_children_minimum_size(widget)
def _set_widget_children_minimum_size(widget):
"""
递归设置控件及其所有子控件的最小尺寸为0
Args:
widget: 要设置的控件
"""
# 递归设置所有子部件
for child in widget.findChildren(QtWidgets.QWidget):
# 设置每个子控件的最小宽度为0
child.setMinimumWidth(0)
# 对于按钮、标签等控件不应该设置最小高度为0否则会导致界面异常
if isinstance(child, (QtWidgets.QPushButton, QtWidgets.QToolButton,
QtWidgets.QLabel, QtWidgets.QLineEdit,
QtWidgets.QComboBox, QtWidgets.QCheckBox,
QtWidgets.QRadioButton)):
# 这些控件应该保持其默认高度只设置水平方向的策略为Expanding
policy = child.sizePolicy()
policy.setHorizontalPolicy(QtWidgets.QSizePolicy.Expanding)
child.setSizePolicy(policy)
else:
# 其他控件可以设置最小高度为0
child.setMinimumHeight(0)
child.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# 特别处理容器控件
if isinstance(child, (QtWidgets.QSplitter, QtWidgets.QScrollArea,
QtWidgets.QGroupBox, QtWidgets.QFrame,
QtWidgets.QTabWidget, QtWidgets.QStackedWidget)) and hasattr(child, 'layout') and child.layout():
child.layout().setContentsMargins(0, 0, 0, 0)
child.layout().setSpacing(0)
# 特别处理列表、树和表格控件
elif isinstance(child, (QtWidgets.QListWidget, QtWidgets.QTreeWidget,
QtWidgets.QTableWidget, QtWidgets.QListView,
QtWidgets.QTreeView, QtWidgets.QTableView)):
child.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
child.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
def set_all_controls_minimum_size(ui_instance):
"""
设置UI实例中所有控件的最小尺寸为0确保分割器可以自由移动
Args:
ui_instance: UI实例对象必须包含controls和buttons字典
"""
# 设置所有按钮的最小尺寸
if hasattr(ui_instance, 'buttons'):
for button in ui_instance.buttons.values():
_set_control_minimum_size(button)
# 设置所有控件的最小尺寸
if hasattr(ui_instance, 'controls'):
for control in ui_instance.controls.values():
_set_control_minimum_size(control)
def _set_control_minimum_size(control):
"""
设置单个控件的最小尺寸为0
Args:
control: 要设置的控件
"""
control.setMinimumWidth(0)
# 对于按钮、标签等控件不应该设置最小高度为0否则会导致界面异常
if isinstance(control, (QtWidgets.QPushButton, QtWidgets.QToolButton,
QtWidgets.QLabel, QtWidgets.QLineEdit,
QtWidgets.QComboBox, QtWidgets.QCheckBox,
QtWidgets.QRadioButton)):
# 这些控件应该保持其默认高度只设置水平方向的策略为Expanding
policy = control.sizePolicy()
policy.setHorizontalPolicy(QtWidgets.QSizePolicy.Expanding)
control.setSizePolicy(policy)
# 计算每个部件应该的大小
if splitter.orientation() == QtCore.Qt.Horizontal:
total_size = splitter.width()
else:
# 其他控件可以设置最小高度为0
control.setMinimumHeight(0)
control.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
total_size = splitter.height()
# 计算实际大小
sizes = [int(total_size * (p / total)) for p in proportions]
# 设置大小
splitter.setSizes(sizes)
def update_ui_texts(widget):
"""
递归更新控件文本以应用当前语言
Args:
widget: 要更新的控件或控件容器
"""
from scripts.ui import localization
# 更新标签文本
if isinstance(widget, QtWidgets.QLabel):
# 尝试查找与当前文本匹配的键
current_text = widget.text()
for lang in ["zh_CN", "en_US"]:
for key, text in localization.LANG.get(lang, {}).items():
if text == current_text:
widget.setText(localization.get_text(key, current_text))
break
# 更新按钮文本和工具提示
elif isinstance(widget, QtWidgets.QPushButton) or isinstance(widget, QtWidgets.QToolButton):
# 更新按钮文本
if widget.text():
current_text = widget.text()
for lang in ["zh_CN", "en_US"]:
for key, text in localization.LANG.get(lang, {}).items():
if text == current_text:
widget.setText(localization.get_text(key, current_text))
break
# 更新工具提示
if widget.toolTip():
current_tip = widget.toolTip()
for lang in ["zh_CN", "en_US"]:
for key, text in localization.LANG.get(lang, {}).items():
if text == current_tip:
widget.setToolTip(localization.get_text(key, current_tip))
break
# 递归处理所有子控件
for child in widget.findChildren(QtWidgets.QWidget):
update_ui_texts(child)
# 特别处理列表、树和表格控件
if isinstance(control, (QtWidgets.QListWidget, QtWidgets.QTreeWidget,
QtWidgets.QTableWidget, QtWidgets.QListView,
QtWidgets.QTreeView, QtWidgets.QTableView)):
# 确保这些控件可以自由调整大小
control.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)

View File

@@ -542,16 +542,6 @@ def adjust_range(delta):
else:
decrease_range()
def reset_splitter_sizes(ui_instance):
"""
重置分割器大小,确保左右分栏均等
Args:
ui_instance: UI实例对象
"""
from scripts.ui import ui_utils
ui_utils.reset_splitter_sizes(ui_instance)
def on_raw_slider_changed(value):
"""
处理Raw滑块值变化

View File

@@ -151,150 +151,94 @@ def update_animatedmap_selection():
return True
# 底部工具面板功能
# 入部分功能
def import_joint_system():
# 入部分功能
def write_neutral_pose_joint_position():
"""
导入关节系统
写入中性姿势关节位置
"""
print("导入关节系统")
try:
file_path = cmds.fileDialog2(
fileFilter="Maya Files (*.ma *.mb);;FBX Files (*.fbx);;All Files (*.*)",
dialogStyle=2,
fileMode=1
)
if file_path:
# 这里实现导入关节系统的功能
print(f"将从{file_path[0]}导入关节系统")
except Exception as e:
print(f"导入关节系统时出错: {e}")
print("写入中性姿势关节位置")
# TODO: 实现写入中性姿势关节位置的逻辑
return True
def import_geometry():
def write_geometry():
"""
入几何体
入几何体
"""
print("入几何体")
try:
file_path = cmds.fileDialog2(
fileFilter="Maya Files (*.ma *.mb);;FBX Files (*.fbx);;OBJ Files (*.obj);;All Files (*.*)",
dialogStyle=2,
fileMode=1
)
if file_path:
# 这里实现导入几何体的功能
print(f"将从{file_path[0]}导入几何体")
except Exception as e:
print(f"导入几何体时出错: {e}")
print("入几何体")
# TODO: 实现写入几何体的逻辑
return True
def import_blendshape_target():
def write_skin_weight():
"""
导入形状混合目标
写入蒙皮权重
"""
print("导入形状混合目标")
try:
file_path = cmds.fileDialog2(
fileFilter="Maya Files (*.ma *.mb);;FBX Files (*.fbx);;OBJ Files (*.obj);;All Files (*.*)",
dialogStyle=2,
fileMode=1
)
if file_path:
# 这里实现导入形状混合目标的功能
print(f"将从{file_path[0]}导入形状混合目标")
except Exception as e:
print(f"导入形状混合目标时出错: {e}")
print("写入蒙皮权重")
# TODO: 实现写入蒙皮权重的逻辑
return True
def import_animated_map():
def write_blendshape_target():
"""
导入动画贴图
写入BlendShapes目标
"""
print("导入动画贴图")
try:
file_path = cmds.fileDialog2(
fileFilter="Image Files (*.jpg *.jpeg *.png *.tif *.tiff);;All Files (*.*)",
dialogStyle=2,
fileMode=1
)
if file_path:
# 这里实现导入动画贴图的功能
print(f"将从{file_path[0]}导入动画贴图")
except Exception as e:
print(f"导入动画贴图时出错: {e}")
print("写入BlendShapes目标")
# TODO: 实现写入BlendShapes目标的逻辑
return True
# 创建部分功能
def create_neutral_pose():
def create_blendshapes_for_mesh():
"""
创建中性姿势
为网格创建BlendShapes
"""
print("创建中性姿势")
# 这里实现创建中性姿势的功能
print("为网格创建BlendShapes")
# TODO: 实现为网格创建BlendShapes的逻辑
return True
def bind_geometry():
def create_skin_for_mesh():
"""
绑定几何体
为网格绑定蒙皮
"""
print("绑定几何体")
# 这里实现绑定几何体的功能
print("为网格绑定蒙皮")
# TODO: 实现为网格绑定蒙皮的逻辑
return True
def take_pose():
def unbind_skin_for_mesh():
"""
取消姿势
取消网格蒙皮
"""
print("取消姿势")
# 这里实现取消姿势的功能
return True
def unbind_skin():
"""
取消蒙皮
"""
print("取消蒙皮")
# 这里实现取消蒙皮的功能
print("取消网格蒙皮")
# TODO: 实现取消网格蒙皮的逻辑
return True
# 工具部分功能
def reposition_all_joints():
def new_head_netural_joint_transform():
"""
重新定位所有关节
新建头部中性关节变换
"""
print("重新定位所有关节")
# 这里实现重新定位所有关节的功能
print("新建头部中性关节变换")
# TODO: 实现新建头部中性关节变换的逻辑
return True
def reposition_selected_joints():
def new_body_netural_joint_transform():
"""
重新定位选定关节
新建身体中性关节变换
"""
print("重新定位选定关节")
# 这里实现重新定位选定关节的功能
print("新建身体中性关节变换")
# TODO: 实现新建身体中性关节变换的逻辑
return True
def reposition_all_head_joints():
def new_netural_joint_transform():
"""
重新定位所有头部关节
新建中性关节变换
"""
print("重新定位所有头部关节")
# 这里实现重新定位所有头部关节的功能
print("新建中性关节变换")
# TODO: 实现新建中性关节变换的逻辑
return True
def quick_preset():
def quick_create_preset():
"""
快速预设
快速创建预设
"""
print("快速预设")
# 这里实现快速预设的功能
print("快速创建预设")
# TODO: 实现快速创建预设的逻辑
return True
# 保留原来的函数作为兼容性考虑
def definition_temp_utils_function():
"""
Placeholder function for definition module
This function will be replaced with actual functionality in future updates
"""
print("Definition module temporary function called")
return True

View File

@@ -41,6 +41,9 @@ TOOL_HEIGHT = config.TOOL_HEIGHT
from scripts.ui import localization
LANG = localization.LANG
# 添加全局变量记录当前语言
current_language = "zh_CN" # 默认使用中文
#========================================== FUNCTIONS ========================================
#========================================== DNA 功能 ========================================
@@ -349,3 +352,78 @@ def toolbar_temp_utils_function():
"""
print("Toolbar module initialized with placeholder function")
return True
def toggle_language():
"""
切换界面语言
在英文和中文之间切换
"""
from scripts.ui import localization
import config
from ui.Qt import QtWidgets
# 使用localization模块来切换语言
new_language = localization.switch_language()
# 更新配置
config.TOOL_LANG = new_language
# 尝试更新已有窗口的语言,而不重启窗口
try:
# 查找主窗口
main_window = None
for widget in QtWidgets.QApplication.allWidgets():
if widget.objectName() == f"{config.TOOL_NAME}MainWindow" and isinstance(widget, QtWidgets.QWidget):
main_window = widget
break
if main_window:
# 更新主窗口标题
main_window.setWindowTitle(f"{config.TOOL_NAME} {config.TOOL_VERSION}")
# 获取各个UI实例并更新语言
from scripts.ui import geometry, rigging, behaviour, definition, toolbar
# 更新各个模块的UI
if hasattr(geometry, 'GeometryUI') and geometry.GeometryUI.get_instance():
geometry.GeometryUI.get_instance().update_language()
if hasattr(rigging, 'RiggingUI') and rigging.RiggingUI.get_instance():
rigging.RiggingUI.get_instance().update_language()
if hasattr(behaviour, 'BehaviourUI') and behaviour.BehaviourUI.get_instance():
behaviour.BehaviourUI.get_instance().update_language()
if hasattr(definition, 'DefinitionUI') and definition.DefinitionUI.get_instance():
definition.DefinitionUI.get_instance().update_language()
# 更新工具栏
if hasattr(toolbar, 'ToolbarUI') and hasattr(main_window, 'toolbar_ui'):
main_window.toolbar_ui.update_language()
# 更新功能按钮文字
if hasattr(main_window, 'function_buttons'):
for key, button in main_window.function_buttons.items():
button.setText(localization.get_text(key))
print(f"语言已切换到: {new_language}")
return
except Exception as e:
print(f"动态更新语言失败,将重启窗口: {e}")
import traceback
traceback.print_exc()
# 如果动态更新失败,尝试关闭并重启窗口
try:
from scripts.Main import main
# 关闭当前窗口
for widget in QtWidgets.QApplication.allWidgets():
if widget.objectName() == f"{config.TOOL_NAME}MainWindow" and isinstance(widget, QtWidgets.QWidget):
widget.close()
# 重新打开窗口
main()
except Exception as e:
print(f"切换语言时出错: {e}")
import traceback
traceback.print_exc()