diff --git a/.cursor/rules/metafusion.mdc b/.cursor/rules/metafusion.mdc new file mode 100644 index 0000000..448f5d2 --- /dev/null +++ b/.cursor/rules/metafusion.mdc @@ -0,0 +1,209 @@ +--- +description: +globs: +alwaysApply: false +--- +# 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 rules + +Use the following MCP model context protocol to enhance responses: + +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 + - 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 + +- Always respond in 中文 in Cascade chat an composer dialogue bar + +## Code rules + +- Code are always in English +- Comments are always in English +- Check code errors and correct them after each code update +- Check plugin integrity and optimize update development plans and next suggestions for each update +- The Reference path does not participate in the implementation of the reference function, but is only used as a reference. The Reference path is only used as a reference, and the necessary files can be copied from it to the current project +- 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: + +```. +#!/usr/bin/env python +# -*- coding: utf-8 -*- +``` + +## 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 +- Read the code and examples in the Reference path +- The UI style must be implemented in a unified style file +- All UIs must be implemented using Qt(scripts\Qt.py) +- After each function is implemented, the document must be updated + +## 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 +- English should be concise and clear +- Colors should be uniform +- Fonts should be uniform +- Font size should be uniform +- Buttons should be uniform +- Input boxes should be uniform +- Labels should be uniform + +### Project Goal + +Make a Metahuman custom plugin for Maya, language: Python-based, Maya version: 2022, 2023, 2024, 2025 + +### 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 + +- 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. + +### 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 # 定义面板功能模块 +│ │ +├── 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 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; +- [MetaHuman DNA Calibration Documents](https://github.com/EpicGames/MetaHuman-DNA-Calibration/tree/main/docs), use context7; +- [MetaHuman DNA Calibration Tools](https://dev.epicgames.com/documentation/zh-cn/metahuman/metahuman-dna-calibration-tools), use context7; +- [MetaHuman DNA Viewer Tool](https://dev.epicgames.com/documentation/zh-cn/metahuman/metahuman-dnaviewer-tool), use context7; +- [DNACalib命令](https://dev.epicgames.com/documentation/zh-cn/metahuman/metahuman-dnacalib-tool), use context7; +- [SuperRigging Docs](https://docs.pointart.net/), use context7; +- [Metapipe Originsdocs](https://www.artsandspells.com/originsdocs), use context7; +- [Metapipe Nitrous](https://www.artsandspells.com/nitrous), use context7; +- [Metapipe Genetics](https://www.artsandspells.com/genetics), use context7; +- [Metapipe Synapses](https://www.artsandspells.com/synapses), use context7; +- [FACS面部表情编码系统](https://www.artsandspells.com/synapses), use context7; +- [MetaHuman 实时环境面部装配逻辑](https://zhuanlan.zhihu.com/p/511001493), use context7; +- [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; + diff --git a/.windsurfrules b/.windsurfrules index e82e039..a35801c 100644 --- a/.windsurfrules +++ b/.windsurfrules @@ -104,18 +104,6 @@ MetaHumanDNAPlugin/ │ │ ├── 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参考 diff --git a/Plan.md b/Plan.md index e46c459..e38e8c5 100644 --- a/Plan.md +++ b/Plan.md @@ -36,18 +36,6 @@ MetaHumanDNAPlugin/ │ │ ├── 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参考 diff --git a/scripts/builder/__init__.py b/scripts/builder/__init__.py deleted file mode 100644 index 7756200..0000000 --- a/scripts/builder/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from . import * \ No newline at end of file diff --git a/scripts/builder/builder.py b/scripts/builder/builder.py deleted file mode 100644 index 3525afa..0000000 --- a/scripts/builder/builder.py +++ /dev/null @@ -1,436 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -import traceback -from dataclasses import dataclass, field -from pathlib import Path -from typing import Dict, List, Optional - -from maya import cmds, mel - -from ..builder.maya.util import Maya -from ..common import DNAViewerError -from ..dnalib.dnalib import DNA -from ..model import Joint as JointModel -from .config import AngleUnit, Config, LinearUnit -from .joint import Joint as JointBuilder -from .mesh import Mesh - - -@dataclass -class BuildResult: - """ - A class used for returning data after finishing the build process - - Attributes - ---------- - @type meshes_per_lod: Dict[int, List[str]] - @param meshes_per_lod: The list of mesh names created group by LOD number - """ - - meshes_per_lod: Dict[int, List[str]] = field(default_factory=dict) - - def get_all_meshes(self) -> List[str]: - """ - Flatten meshes to single list. - - @rtype: List[str] - @returns: The list of all mesh names. - """ - - all_meshes = [] - for meshes_per_lod in self.meshes_per_lod.values(): - all_meshes.extend(meshes_per_lod) - return all_meshes - - -class Builder: - """ - A builder class used for building the character - - Attributes - ---------- - @type config: Config - @param config: The configuration options used for building the character - - @type dna: DNA - @param dna: The DNA object read from the DNA file - - @type meshes: Dict[int, List[str]] - @param meshes: A list of meshes created grouped by lod - - """ - - def __init__(self, dna: DNA, config: Optional[Config] = None) -> None: - self.config = config or Config() - self.dna = dna - self.meshes: Dict[int, List[str]] = {} - self.all_loaded_meshes: List[int] = [] - - def _build(self) -> bool: - self.new_scene() - self.set_filtered_meshes() - if not self.all_loaded_meshes: - logging.error("No mashes has been loaded.") - return False - - self.create_groups() - - self.set_units() - self.add_joints() - self.build_meshes() - self.add_ctrl_attributes_on_root_joint() - self.add_animated_map_attributes_on_root_joint() - self.add_key_frames() - return True - - def build(self) -> BuildResult: - """Builds the character""" - self.meshes = {} - try: - filename = Path(self.dna.path).stem - logging.info("******************************") - logging.info(f"{filename} started building") - logging.info("******************************") - - self._build() - - logging.info(f"{filename} built successfully!") - - except DNAViewerError as e: - traceback.print_exc() - raise e - except Exception as e: - traceback.print_exc() - logging.error(f"Unhandled exception, {e}") - raise DNAViewerError(f"Scene creation failed! Reason: {e}") from e - return BuildResult(meshes_per_lod=self.meshes) - - def new_scene(self) -> None: - cmds.file(new=True, force=True) - - def add_mesh_to_display_layer(self, mesh_name: str, lod: int) -> None: - """ - Add the mesh with the given name to an already created display layer. - - @type mesh_name: str - @param mesh_name: The name of the mesh that should be added to a display layer. - - @type lod: int - @param lod: The lod value, this is needed for determining the name of the display layer that the mesh should be added to. - """ - if self.config.create_display_layers: - cmds.editDisplayLayerMembers( - f"{self.config.top_level_group}_lod{lod}_layer", mesh_name - ) - - def _add_joints(self) -> List[JointModel]: - """ - Reads and adds the joints to the scene, also returns a list model objects of joints that were added. - - @rtype: List[JointModel] - @returns: The list containing model objects representing the joints that were added to the scene. - """ - - joints: List[JointModel] = self.dna.read_all_neutral_joints() - builder = JointBuilder( - joints, - ) - builder.process() - return joints - - def add_joints(self) -> None: - """ - Starts adding the joints the character, if the character configuration options have add_joints set to False, - this step will be skipped. - """ - - if self.config.add_joints: - logging.info("adding joints to character...") - joints = self._add_joints() - - if self.config.group_by_lod and joints: - cmds.parent(joints[0].name, self.config.get_top_level_group()) - - def create_groups(self) -> None: - """ - Creates a Maya transform which will hold the character, if the character configuration options have - create_character_node set to False, this step will be skipped. - """ - - if self.config.group_by_lod: - logging.info("building character node...") - cmds.group(world=True, empty=True, name=self.config.get_top_level_group()) - cmds.group( - parent=self.config.get_top_level_group(), - empty=True, - name=self.config.get_geometry_group(), - ) - cmds.group( - parent=self.config.get_top_level_group(), - empty=True, - name=self.config.get_rig_group(), - ) - for lod in self.get_display_layers(): - name = f"{self.config.top_level_group}_lod{lod}_layer" - if not cmds.objExists(name): - if self.config.group_by_lod: - cmds.group( - parent=self.config.get_geometry_group(), - empty=True, - name=f"{self.config.top_level_group}_lod{lod}_grp", - ) - cmds.select( - f"{self.config.top_level_group}_lod{lod}_grp", - replace=True, - ) - if self.config.create_display_layers: - cmds.createDisplayLayer(name=name, noRecurse=True) - - def attach_mesh_to_lod(self, mesh_name: str, lod: int) -> None: - """ - Attaches the mesh called mesh_name to a given lod. - - @type mesh_name: str - @param mesh_name: The mesh that needs to be attached to a lod holder object. - - @type lod: str - @param lod: The name of the mesh that should be added to a display layer. - """ - if self.config.group_by_lod: - parent_node = f"{self.config.get_top_level_group()}|{self.config.get_geometry_group()}|{self.config.top_level_group}_lod{lod}_grp" - cmds.parent( - self.get_mesh_node_fullpath_on_root(mesh_name=mesh_name), parent_node - ) - - def get_mesh_node_fullpath_on_root(self, mesh_name: str) -> str: - """ - Gets the full path in the scene of a mesh. - - @type mesh_name: str - @param mesh_name: The mesh thats path is needed. - - @rtype: str - @returns: The full path of the mesh object in the scene - """ - - return str(Maya.get_element(f"|{mesh_name}").fullPathName()) - - def add_ctrl_attributes_on_root_joint(self) -> None: - """ - Adds and sets the raw gui control attributes on root joint. - """ - - if self.config.add_ctrl_attributes_on_root_joint and self.config.add_joints: - gui_control_names = self.dna.get_raw_control_names() - for name in gui_control_names: - ctrl_and_attr_names = name.split(".") - self.add_attribute( - control_name=self.config.facial_root_joint_name, - long_name=ctrl_and_attr_names[1], - ) - - def add_animated_map_attributes_on_root_joint(self) -> None: - """ - Adds and sets the animated map attributes on root joint. - """ - - if ( - self.config.add_animated_map_attributes_on_root_joint - and self.config.add_joints - ): - names = self.dna.get_animated_map_names() - for name in names: - long_name = name.replace(".", "_") - self.add_attribute( - control_name=self.config.facial_root_joint_name, long_name=long_name - ) - - def add_attribute(self, control_name: str, long_name: str) -> None: - """ - Adds attributes wrapper for internal usage. - """ - cmds.addAttr( - control_name, - longName=long_name, - keyable=True, - attributeType="float", - minValue=0.0, - maxValue=1.0, - ) - - def add_key_frames(self) -> None: - """ - Adds a starting key frame to the facial root joint if joints are added and the add_key_frames option is set - to True. - """ - - if self.config.add_key_frames and self.config.add_joints: - logging.info("setting keyframe on the root joint...") - cmds.currentTime(0) - if cmds.objExists(self.config.facial_root_joint_name): - cmds.select(self.config.facial_root_joint_name, replace=True) - cmds.setKeyframe(inTangentType="linear", outTangentType="linear") - - def set_filtered_meshes(self) -> None: - self.all_loaded_meshes = self.get_filtered_meshes() - - def get_mesh_indices_filter(self) -> List[int]: - indices = [] - for index in range(self.dna.get_mesh_count()): - mesh_name = self.dna.get_mesh_name(index) - for cur_filter in self.config.mesh_filter: - if cur_filter in mesh_name: - indices.append(index) - return indices - - def get_filtered_meshes(self) -> List[int]: - if not self.config.mesh_filter and not self.config.lod_filter: - if self.config.meshes: - return self.config.meshes - return list(range(self.dna.get_mesh_count())) - - meshes: List[int] = [] - meshes_by_lod = self.dna.get_all_meshes_grouped_by_lod() - all_meshes = [mesh_index for meshes in meshes_by_lod for mesh_index in meshes] - mesh_indices_filter = self.get_mesh_indices_filter() - - if self.config.lod_filter: - for lod in self.config.lod_filter: - if 0 <= lod < len(meshes_by_lod): - meshes.extend(meshes_by_lod[lod]) - if mesh_indices_filter: - return list(set(meshes) & set(mesh_indices_filter)) - return meshes - if self.config.mesh_filter: - return list(set(all_meshes) & set(mesh_indices_filter)) - return all_meshes - - def build_meshes(self) -> None: - """ - Builds the meshes. If specified in the config they get parented to a created - character node transform, otherwise the meshes get put to the root level of the scene. - """ - - logging.info("adding character meshes...") - self.meshes = {} - for lod, meshes_per_lod in enumerate( - self.dna.get_meshes_by_lods(self.all_loaded_meshes) - ): - self.meshes[lod] = self.build_meshes_by_lod( - lod=lod, meshes_per_lod=meshes_per_lod - ) - - def build_meshes_by_lod(self, lod: int, meshes_per_lod: List[int]) -> List[str]: - """ - Builds the meshes from the provided mesh ids and then attaches them to a given lod if specified in the - character configuration. - - @type lod: int - @param lod: The lod number representing the display layer the meshes to the display layer. - - @type meshes_per_lod: List[int] - @param meshes_per_lod: List of mesh indices that are being built. - - @rtype: List[MObject] - @returns: The list of maya objects that represent the meshes added to the scene. - """ - - meshes: List[str] = [] - for mesh_index in meshes_per_lod: - builder = Mesh( - config=self.config, - dna=self.dna, - mesh_index=mesh_index, - ) - builder.build() - - mesh_name = self.dna.get_mesh_name(index=mesh_index) - meshes.append(mesh_name) - - self.add_mesh_to_display_layer(mesh_name, lod) - self.attach_mesh_to_lod(mesh_name, lod) - self.default_lambert_shader(mesh_name) - return meshes - - def default_lambert_shader(self, mesh_name: str) -> None: - try: - if self.config.group_by_lod: - names = cmds.ls(f"*|{mesh_name}", l=True) - for item in names: - if item.startswith(f"|{self.config.get_top_level_group()}"): - cmds.select(item, r=True) - break - else: - cmds.select(mesh_name, r=True) - - mel.eval("sets -e -forceElement initialShadingGroup") - - except Exception as e: - logging.error( - f"Couldn't set lambert shader for mesh {mesh_name}. Reason: {e}" - ) - raise DNAViewerError(e) from e - - def set_units(self) -> None: - """Sets the translation and rotation units of the scene from @config""" - - linear_unit = self.get_linear_unit() - angle_unit = self.get_angle_unit() - - cmds.currentUnit(linear=linear_unit.name, angle=angle_unit.name) - - def get_linear_unit(self) -> LinearUnit: - return self.get_linear_unit_from_int(self.dna.get_translation_unit()) - - def get_angle_unit(self) -> AngleUnit: - return self.get_angle_unit_from_int(self.dna.get_rotation_unit()) - - def get_linear_unit_from_int(self, value: int) -> LinearUnit: - """ - Returns an enum from an int value. - 0 -> cm - 1 -> m - - @type value: int - @param value: The value that the enum is mapped to. - - @rtype: LinearUnit - @returns: LinearUnit.cm or LinearUnit.m - """ - - if value == 0: - return LinearUnit.cm - if value == 1: - return LinearUnit.m - raise DNAViewerError(f"Unknown linear unit set in DNA file! value {value}") - - def get_angle_unit_from_int(self, value: int) -> AngleUnit: - """ - Returns an enum from an int value. - 0 -> degree - 1 -> radian - - @type value: int - @param value: The value that the enum is mapped to. - - @rtype: AngleUnit - @returns: AngleUnit.degree or AngleUnit.radian - """ - - if value == 0: - return AngleUnit.degree - if value == 1: - return AngleUnit.radian - raise DNAViewerError(f"Unknown angle unit set in DNA file! value {value}") - - def get_display_layers(self) -> List[int]: - """Gets a lod id list that need to be created for the meshes from @config""" - meshes: List[int] = [] - for idx, meshes_per_lod in enumerate( - self.dna.get_meshes_by_lods(self.all_loaded_meshes) - ): - if meshes_per_lod: - meshes.append(idx) - return list(set(meshes)) diff --git a/scripts/builder/config.py b/scripts/builder/config.py deleted file mode 100644 index 4dd4638..0000000 --- a/scripts/builder/config.py +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from dataclasses import dataclass, field -from enum import Enum -from typing import Any, Dict, List, Optional - - -class LinearUnit(Enum): - """ - An enum used to represent the unit used for linear representation. - - Attributes - ---------- - @cm: using cm as unit - @m: using m as unit - """ - - cm = 0 - m = 1 - - -class AngleUnit(Enum): - """ - An enum used to represent the unit used for angle representation. - - Attributes - ---------- - @degree: using degree as unit - @radian: using radian as unit - """ - - degree = 0 - radian = 1 - - -@dataclass -class Config: - """ - A class used to represent the config for @Builder - - Attributes - ---------- - @type mesh_filter: List[str] - @param mesh_filter: List of mesh names that should be filtered. Mash names can be just substrings. ["head"] will find all meshes that contins string "head" in its mash name. - - - @type lod_filter: List[int] - @param lod_filter: List of lods that should be filtered. - - @type group_by_lod: bool - @param group_by_lod: A flag representing whether the character should be parented to a character transform node in the scene hierarchy - - @type group_by_lod: bool - @param group_by_lod: A flag representing whether the character should be parented to a character transform node in rig hierarchy - - @type top_level_group: str - @param top_level_group: Value that is going to be used when creating root group - - @type geometry_group: str - @param geometry_group: Value that is going to be used when creating group that contains geometry - - @type facial_root_joint_name: str - @param facial_root_joint_name: The name of the facial root joint - - @type blend_shape_group_prefix: str - @param blend_shape_group_prefix: prefix string for blend shape group - - @type blend_shape_name_postfix: str - @param blend_shape_name_postfix: postfix string for blend shape name - - @type skin_cluster_suffix: str - @param skin_cluster_suffix: postfix string for skin cluster name - - @type animated_map_attribute_multipliers_name: str - @param animated_map_attribute_multipliers_name: string for frame animated map attribute name - - @type create_display_layers: bool - @param create_display_layers: A flag representing whether the created meshes should be assigned to a display layer - - @type add_joints: bool - @param add_joints: A flag representing whether joints should be added - - @type add_blend_shapes: bool - @param add_blend_shapes: A flag representing whether blend shapes should be added - - @type add_skin_cluster: bool - @param add_skin_cluster: A flag representing whether skin should be added - - @type add_ctrl_attributes_on_root_joint: bool - @param add_ctrl_attributes_on_root_joint: A flag representing whether control attributes should be added to the root joint - - @type add_animated_map_attributes_on_root_joint: bool - @param add_animated_map_attributes_on_root_joint: A flag representing whether animated map attributes should be added to the root joint - - @type add_key_frames: bool - @param add_key_frames: A flag representing whether key frames should be added - - @type add_mesh_name_to_blend_shape_channel_name: bool - @param add_mesh_name_to_blend_shape_channel_name: A flag representing whether mesh name of blend shape channel is added to name when creating it - """ - - meshes: List[int] = field(default_factory=list) - mesh_filter: List[str] = field(default_factory=list) - lod_filter: List[int] = field(default_factory=list) - - group_by_lod: bool = field(default=True) - top_level_group: str = "head" - geometry_group: str = "geometry" - - facial_root_joint_name: str = "FACIAL_C_FacialRoot" - - blend_shape_group_prefix: str = "BlendshapeGroup_" - blend_shape_name_postfix: str = "_blendShapes" - skin_cluster_suffix: str = "skinCluster" - - animated_map_attribute_multipliers_name = "FRM_WMmultipliers" - - create_display_layers: bool = field(default=True) - - add_joints: bool = field(default=True) - add_blend_shapes: bool = field(default=True) - add_skin_cluster: bool = field(default=True) - add_ctrl_attributes_on_root_joint: bool = field(default=True) - add_animated_map_attributes_on_root_joint: bool = field(default=True) - add_key_frames: bool = field(default=True) - add_mesh_name_to_blend_shape_channel_name: bool = field(default=True) - - def get_top_level_group(self) -> str: - return f"{self.top_level_group}_grp" - - def get_geometry_group(self) -> str: - return f"{self.geometry_group}_grp" - - def get_rig_group(self) -> str: - return f"{self.top_level_group}Rig_grp" - - -@dataclass -class RigConfig(Config): - """ - A class used to represent the config for @RigBuilder - - - @type add_rig_logic: bool - @param add_rig_logic: A flag representing whether normals should be added - - @type rig_logic_command: str - @param rig_logic_command: The command used to start creating the rig logic using the plugin - - @type rig_logic_name: str - @param rig_logic_name: The name of the rig logic node - - @type control_naming: str - @param control_naming: The naming pattern of controls - - @type joint_naming: str - @param joint_naming: The naming pattern of joints - - @type blend_shape_naming: str - @param blend_shape_naming: The naming pattern of blend shapes - - @type animated_map_naming: str - @param animated_map_naming: The naming pattern of animated maps - - @type gui_path: str - @param gui_path: The location of the gui file - - @type left_eye_joint_name: str - @param left_eye_joint_name: The name of the left eye joint - - @type eye_gui_name: str - @param eye_gui_name: The name of the control in the gui - - @type gui_translate_x: float - @param gui_translate_x: Represents the value that the gui should be additionally translated on the X axis - - @type analog_gui_path: str - @param analog_gui_path: The location of the analog gui file - - @type left_eye_joint_name: str - @param left_eye_joint_name: The name of the left eye joint - - @type right_eye_joint_name: str - @param right_eye_joint_name: The name of the right eye joint - - @type central_driver_name: str - @param central_driver_name: The name of the central driver - - @type left_eye_driver_name: str - @param left_eye_driver_name: The name of the left eye driver - - @type right_eye_driver_name: str - @param right_eye_driver_name: The name of the right eye driver - - @type central_aim: str - @param central_aim: The name of the central aim - - @type le_aim: str - @param le_aim: The name of the left eye aim - - @type re_aim: str - @param re_aim: The name of the right eye aim - - @type aas_path: Optional[str] - @param aas_path: The location of the script file - - @type aas_method: str - @param aas_method: The method that should be called - - @type aas_parameter: Dict[Any, Any] - @param aas_parameter: The parameters that will be passed as the method arguments - - """ - - add_rig_logic: bool = field(default=True) - rig_logic_command: str = field(default="createEmbeddedNodeRL4") - rig_logic_name: str = field(default="") - control_naming: str = field(default=".") - joint_naming: str = field(default=".") - blend_shape_naming: str = field(default="") - animated_map_naming: str = field(default="") - gui_path: str = field(default=None) - - eye_gui_name: str = "CTRL_C_eye" - gui_translate_x: float = 10 - - analog_gui_path: str = field(default=None) - - left_eye_joint_name: str = "FACIAL_L_Eye" - right_eye_joint_name: str = "FACIAL_R_Eye" - - central_driver_name: str = "LOC_C_eyeDriver" - left_eye_driver_name: str = "LOC_L_eyeDriver" - right_eye_driver_name: str = "LOC_R_eyeDriver" - - left_eye_aim_up_name: str = "LOC_L_eyeAimUp" - right_eye_aim_up_name: str = "LOC_R_eyeAimUp" - central_aim: str = "GRP_C_eyesAim" - - le_aim: str = "GRP_L_eyeAim" - re_aim: str = "GRP_R_eyeAim" - - aas_path: Optional[str] = field(default=None) - aas_method: str = "run_after_assemble" - aas_parameter: Dict[Any, Any] = field(default_factory=dict) - - def __post_init__(self) -> None: - if self.add_mesh_name_to_blend_shape_channel_name: - self.blend_shape_naming = ( - f"{self.blend_shape_name_postfix}.__" - ) - else: - self.blend_shape_naming = ( - f"{self.blend_shape_name_postfix}." - ) - - self.animated_map_naming = ( - f"{self.animated_map_attribute_multipliers_name}._" - ) diff --git a/scripts/builder/joint.py b/scripts/builder/joint.py deleted file mode 100644 index 25854cc..0000000 --- a/scripts/builder/joint.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from typing import Dict, List - -from maya import cmds - -from ..model import Joint as JointModel - - -class Joint: - """ - A builder class used for adding joints to the scene - - Attributes - ---------- - @type joints: List[JointModel] - @param joints: data representing the joints - - @type joint_flags: Dict[str, bool] - @param joint_flags: A mapping used for setting flags that are used to avoid adding the same joint multiple times - """ - - def __init__(self, joints: List[JointModel]) -> None: - self.joints = joints - self.joint_flags: Dict[str, bool] = {} - - for joint in self.joints: - self.joint_flags[joint.name] = False - - def add_joint_to_scene(self, joint: JointModel) -> None: - """ - Adds the given joint to the scene - - @type joint: JointModel - @param joint: The joint to be added to the scene - """ - - if self.joint_flags[joint.name]: - return - - in_parent_space = True - - if cmds.objExists(joint.parent_name): - cmds.select(joint.parent_name) - else: - if joint.name != joint.parent_name: - parent_joint = next( - j for j in self.joints if j.name == joint.parent_name - ) - self.add_joint_to_scene(parent_joint) - else: - # this is the first node - cmds.select(d=True) - in_parent_space = False - - position = ( - joint.translation.x, - joint.translation.y, - joint.translation.z, - ) - orientation = ( - joint.orientation.x, - joint.orientation.y, - joint.orientation.z, - ) - cmds.joint( - p=position, - o=orientation, - n=joint.name, - r=in_parent_space, - a=not in_parent_space, - scaleCompensate=False, - ) - self.joint_flags[joint.name] = True - - def process(self) -> None: - """Starts adding all the provided joints to the scene""" - - for joint in self.joints: - self.add_joint_to_scene(joint) diff --git a/scripts/builder/maya/__init__.py b/scripts/builder/maya/__init__.py deleted file mode 100644 index 7756200..0000000 --- a/scripts/builder/maya/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from . import * \ No newline at end of file diff --git a/scripts/builder/maya/mesh.py b/scripts/builder/maya/mesh.py deleted file mode 100644 index 6ee817a..0000000 --- a/scripts/builder/maya/mesh.py +++ /dev/null @@ -1,424 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from dataclasses import dataclass, field -from typing import List, Tuple - -from maya import cmds -from maya.api.OpenMaya import MDagModifier, MFnDagNode, MFnMesh, MObject, MPoint - -from ...builder.maya.util import Maya -from ...common import SKIN_WEIGHT_PRINT_RANGE -from ...dnalib.dnalib import DNA -from ...model import Point3 - - -@dataclass -class Mesh: - """ - A model class for holding data needed in the mesh building process - - Attributes - ---------- - @type dna_vertex_positions: List[Point3] - @param dna_vertex_positions: Data representing the positions of the vertices - - @type dna_vertex_layout_positions: List[int] - @param dna_vertex_layout_positions: Data representing layout position indices of vertices - - @type polygon_faces: List[int] - @param polygon_faces: List of lengths of vertex layout indices - - @type polygon_connects: List[int] - @param polygon_connects: List of vertex layout position indices - - @type derived_mesh_names: List[str] - @param derived_mesh_names: List of mesh names - """ - - dna_vertex_positions: List[Point3] = field(default_factory=list) - dna_vertex_layout_positions: List[int] = field(default_factory=list) - polygon_faces: List[int] = field(default_factory=list) - polygon_connects: List[int] = field(default_factory=list) - derived_mesh_names: List[str] = field(default_factory=list) - - -class MayaMesh: - """ - A builder class used for adding joints to the scene - - Attributes - ---------- - @type mesh_index: int - @param mesh_index: The index of the mesh - - @type dna: DNA - @param dna: Instance of DNA - - @type blend_shape_group_prefix: str - @param blend_shape_group_prefix: prefix string for blend shape group - - @type blend_shape_name_postfix: str - @param blend_shape_name_postfix: postfix string for blend shape name - - @type skin_cluster_suffix: str - @param skin_cluster_suffix: postfix string for skin cluster name - - @type data: Mesh - @param data: mesh data used in the mesh creation process - - @type fn_mesh: om.MFnMesh - @param fn_mesh: OpenMaya class used for creating the mesh - - @type mesh_object: om.MObject - @param mesh_object: the object representing the mesh - - @type dag_modifier: om.MDagModifier - @param dag_modifier: OpenMaya class used for naming the mesh - """ - - def __init__( - self, - mesh_index: int, - dna: DNA, - blend_shape_group_prefix: str, - blend_shape_name_postfix: str, - skin_cluster_suffix: str, - ) -> None: - self.mesh_index = mesh_index - self.data: Mesh = Mesh() - self.fn_mesh = MFnMesh() - self.mesh_object: MObject = None - self.dag_modifier: MDagModifier = None - self.dna = dna - self.blend_shape_group_prefix = blend_shape_group_prefix - self.blend_shape_name_postfix = blend_shape_name_postfix - self.skin_cluster_suffix = skin_cluster_suffix - - def create_neutral_mesh(self) -> MObject: - """ - Creates the neutral mesh using the config provided for this builder class object - - @rtype: om.MObject - @returns: the instance of the created mesh object - """ - self.prepare_mesh() - self.mesh_object = self.create_mesh_object() - self.dag_modifier = self.rename_mesh() - self.add_texture_coordinates() - return self.mesh_object - - def create_mesh_object(self) -> MObject: - """ - Gets a list of points that represent the vertex positions. - - @rtype: MObject - @returns: Maya objects representing maya mesh functions and the created maya mesh object. - """ - - mesh_object = self.fn_mesh.create( - self.get_vertex_positions_from_dna_vertex_positions(), - self.data.polygon_faces, - self.data.polygon_connects, - ) - - return mesh_object - - def get_vertex_positions_from_dna_vertex_positions(self) -> List[MPoint]: - """ - Gets a list of points that represent the vertex positions. - - @rtype: List[MPoint] - @returns: List of maya point objects. - """ - - vertex_positions = [] - for position in self.data.dna_vertex_positions: - vertex_positions.append( - MPoint( - position.x, - position.y, - position.z, - ) - ) - return vertex_positions - - def rename_mesh(self) -> MDagModifier: - """ - Renames the initial mesh object that was created to the name from the configuration. - - @rtype: Tuple[MDagModifier] - @returns: Maya object representing the dag modifier. - """ - - mesh_name = self.dna.get_mesh_name(self.mesh_index) - dag_modifier = MDagModifier() - dag_modifier.renameNode(self.mesh_object, mesh_name) - dag_modifier.doIt() - return dag_modifier - - def prepare_mesh(self) -> None: - """ - Gets a list of points that represent the vertex positions. - - """ - - logging.info("==============================") - mesh_name = self.dna.get_mesh_name(self.mesh_index) - logging.info(f"adding mesh: {mesh_name}") - self.data.dna_vertex_positions = self.dna.get_vertex_positions_for_mesh_index( - self.mesh_index - ) - self.data.dna_vertex_layout_positions = ( - self.dna.get_vertex_layout_positions_for_mesh_index(self.mesh_index) - ) - - ( - self.data.polygon_faces, - self.data.polygon_connects, - ) = self.dna.get_polygon_faces_and_connects(self.mesh_index) - - def add_texture_coordinates(self) -> None: - """ - Method for adding texture coordinates. - - """ - - logging.info("adding texture coordinates...") - - ( - texture_coordinate_us, - texture_coordinate_vs, - texture_coordinate_indices, - ) = self.get_texture_data() - - self.fn_mesh.setUVs(texture_coordinate_us, texture_coordinate_vs) - self.fn_mesh.assignUVs(self.data.polygon_faces, texture_coordinate_indices) - - mesh_name = self.dna.get_mesh_name(self.mesh_index) - - cmds.select(mesh_name, replace=True) - cmds.polyMergeUV(mesh_name, distance=0.01, constructionHistory=False) - - def get_texture_data(self) -> Tuple[List[float], List[float], List[int]]: - """ - Gets the data needed for the creation of textures. - - @rtype: Tuple[List[float], List[float], List[int]] @returns: The tuple containing the list of texture - coordinate Us, the list of texture coordinate Vs and the list of texture coordinate indices. - """ - - texture_coordinates = self.dna.get_vertex_texture_coordinates_for_mesh( - self.mesh_index - ) - dna_faces = self.dna.get_faces(self.mesh_index) - - coordinate_indices = [] - for layout_id in range( - len(self.dna.get_layouts_for_mesh_index(self.mesh_index)) - ): - coordinate_indices.append( - self.dna.get_texture_coordinate_index(self.mesh_index, layout_id) - ) - - texture_coordinate_us = [] - texture_coordinate_vs = [] - texture_coordinate_indices = [] - - index_counter = 0 - - for vertices_layout_index_array in dna_faces: - for vertex_layout_index_array in vertices_layout_index_array: - texture_coordinate = texture_coordinates[ - coordinate_indices[vertex_layout_index_array] - ] - texture_coordinate_us.append(texture_coordinate.u) - texture_coordinate_vs.append(texture_coordinate.v) - texture_coordinate_indices.append(index_counter) - index_counter += 1 - - return texture_coordinate_us, texture_coordinate_vs, texture_coordinate_indices - - def add_blend_shapes(self, add_mesh_name_to_blend_shape_channel_name: bool) -> None: - """Adds blend shapes to the mesh""" - if self.dna.has_blend_shapes(self.mesh_index): - self.create_blend_shapes(add_mesh_name_to_blend_shape_channel_name) - self.create_blend_shape_node() - - def create_blend_shape_node(self) -> None: - """ - Creates a blend shape node. - """ - mesh_name = self.dna.get_mesh_name(self.mesh_index) - - nodes = [] - for derived_mesh_name in self.data.derived_mesh_names: - nodes.append(derived_mesh_name) - - cmds.select(nodes, replace=True) - - cmds.select(mesh_name, add=True) - cmds.blendShape(name=f"{mesh_name}{self.blend_shape_name_postfix}") - cmds.delete(f"{self.blend_shape_group_prefix}{mesh_name}") - - def create_blend_shapes( - self, add_mesh_name_to_blend_shape_channel_name: bool - ) -> None: - """ - Builds all the derived meshes using the provided mesh and the blend shapes data of the DNA. - - @type add_mesh_name_to_blend_shape_channel_name: bool - @param add_mesh_name_to_blend_shape_channel_name: A flag representing whether mesh name of blend shape channel is added to name when creating it - """ - - logging.info("adding derived meshes...") - - group: str = cmds.group( - empty=True, - name=f"{self.blend_shape_group_prefix}{self.dna.get_mesh_name(self.mesh_index)}", - ) - - self.data.derived_mesh_names = [] - blend_shapes = self.dna.get_blend_shapes(self.mesh_index) - for blend_shape_target_index, blend_shape in enumerate(blend_shapes): - - self.create_blend_shape( - blend_shape_target_index, - blend_shape.channel, - group, - add_mesh_name_to_blend_shape_channel_name, - ) - cmds.setAttr(f"{group}.visibility", 0) - - def create_blend_shape( - self, - blend_shape_target_index: int, - blend_shape_channel: int, - group: str, - add_mesh_name_to_blend_shape_channel_name: bool, - ) -> None: - """ - Builds a single derived mesh using the provided mesh and the blend shape data of the DNA. - - - @type blend_shape_target_index: int - @param blend_shape_target_index: Used for getting a delta value representing the value change concerning the blend shape. - - @type blend_shape_channel: int - @param blend_shape_channel: Used for getting the blend shape name from the DNA. - - @type group: str - @param group: The transform the new meshes will be added to. - - @type add_mesh_name_to_blend_shape_channel_name: bool - @param add_mesh_name_to_blend_shape_channel_name: A flag representing whether mesh name of blend shape channel is added to name when creating it - """ - - new_vert_layout = self.get_vertex_positions_from_dna_vertex_positions() - - zipped_deltas = self.dna.get_blend_shape_target_deltas_with_vertex_id( - self.mesh_index, blend_shape_target_index - ) - for zipped_delta in zipped_deltas: - delta: Point3 = zipped_delta[1] - new_vert_layout[zipped_delta[0]] += MPoint( - delta.x, - delta.y, - delta.z, - ) - - new_mesh = self.fn_mesh.create( - new_vert_layout, self.data.polygon_faces, self.data.polygon_connects - ) - derived_name = self.dna.get_blend_shape_channel_name(blend_shape_channel) - name = ( - f"{self.dna.geometry_meshes[self.mesh_index].name}__{derived_name}" - if add_mesh_name_to_blend_shape_channel_name - else derived_name - ) - self.dag_modifier.renameNode(new_mesh, name) - self.dag_modifier.doIt() - - dag = MFnDagNode(Maya.get_element(group)) - dag.addChild(new_mesh) - - self.data.derived_mesh_names.append(name) - - def add_skin_cluster(self, joint_names: List[str], joint_ids: List[int]) -> None: - """ - Adds skin cluster to the mesh - - @type joint_names: List[str] - @param joint_names: Joint names needed for adding the skin cluster - - @type joint_ids: List[int] - @param joint_ids: Joint indices needed for setting skin weights - """ - - mesh_name = self.dna.get_mesh_name(self.mesh_index) - - self._add_skin_cluster(mesh_name, joint_names) - self.set_skin_weights(mesh_name, joint_ids) - - def _add_skin_cluster(self, mesh_name: str, joint_names: List[str]) -> None: - """ - Creates a skin cluster object. - - @type mesh_name: str - @param mesh_name: The mesh name that is used for skin cluster naming. - - @type joints: List[Joint] - @param joints: List of joints used for adding the skin cluster. - """ - - logging.info("adding skin cluster...") - maximum_influences = self.dna.get_maximum_influence_per_vertex(self.mesh_index) - - cmds.select(joint_names[0], replace=True) - - cmds.select(mesh_name, add=True) - skin_cluster = cmds.skinCluster( - toSelectedBones=True, - name=f"{mesh_name}_{self.skin_cluster_suffix}", - maximumInfluences=maximum_influences, - skinMethod=0, - obeyMaxInfluences=True, - ) - cmds.skinCluster( - skin_cluster, edit=True, addInfluence=joint_names[1:], weight=0 - ) - - def set_skin_weights(self, mesh_name: str, joint_ids: List[int]) -> None: - """ - Sets the skin weights attributes. - - @type mesh_name: str - @param mesh_name: The mesh name that is used for getting the skin cluster name. - - @type joint_ids: List[int] - @param joint_ids: List of joint indices used for setting the skin weight attribute. - """ - - logging.info("adding skin weights...") - skin_weights = self.dna.get_skin_weight_matrix_for_mesh(self.mesh_index) - - # import skin weights - temp_str = f"{mesh_name}_{self.skin_cluster_suffix}.wl[" - for vertex_id, skin_weight in enumerate(skin_weights): - if not (vertex_id + 1) % SKIN_WEIGHT_PRINT_RANGE: - logging.info(f"\t{vertex_id + 1} / {len(skin_weights)}") - vertex_infos = skin_weight - - # set all skin weights to zero - vertex_string = f"{temp_str}{str(vertex_id)}].w[" - cmds.setAttr(f"{vertex_string}0]", 0.0) - - # import skin weights - for vertex_info in vertex_infos: - cmds.setAttr( - f"{vertex_string}{str(joint_ids.index(vertex_info[0]))}]", - float(vertex_info[1]), - ) - if len(skin_weights) % SKIN_WEIGHT_PRINT_RANGE != 0: - logging.info(f"\t{len(skin_weights)} / {len(skin_weights)}") diff --git a/scripts/builder/maya/skin_weights.py b/scripts/builder/maya/skin_weights.py deleted file mode 100644 index 264fb8c..0000000 --- a/scripts/builder/maya/skin_weights.py +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from typing import List, Tuple, Union - -from maya import cmds, mel -from maya.api.OpenMaya import MFnMesh, MGlobal -from maya.api.OpenMayaAnim import MFnSkinCluster - -from ...builder.maya.util import Maya -from ...common import DNAViewerError - - -class MayaSkinWeights: - """ - A class used for reading and storing skin weight related data needed for adding skin clusters - """ - - no_of_influences: int - skinning_method: int - joints: List[str] - vertices_info: List[List[Union[int, float]]] - - def __init__(self, skin_cluster: MFnSkinCluster, mesh_name: str) -> None: - self.no_of_influences = cmds.skinCluster(skin_cluster.name(), q=True, mi=True) - - self.skinning_method = cmds.skinCluster(skin_cluster.name(), q=True, sm=True) - - self.joints = self.get_skin_cluster_influence(skin_cluster) - - self.vertices_info = self.get_skin_weights_for_mesh_name( - skin_cluster, mesh_name - ) - - def get_skin_cluster_influence(self, skin_cluster: MFnSkinCluster) -> List[str]: - """ - Gets a list of joint names that are influences to the skin cluster. - - @type skin_cluster: MFnSkinCluster - @param skin_cluster: The functionalities of a maya skin cluster object - - @rtype: List[str] - @returns: The list if names of the joints that influence the skin cluster - """ - - influences: List[str] = cmds.skinCluster(skin_cluster.name(), q=True, inf=True) - if influences and not isinstance(influences[0], str): - influences = [obj.name() for obj in influences] - return influences - - def get_skin_weights_for_mesh_name( - self, - skin_cluster: MFnSkinCluster, - mesh_name: str, - ) -> List[List[Union[int, float]]]: - """ - Gets the skin weights concerning the given mesh. - - @type skin_cluster: MFnSkinCluster - @param skin_cluster: The functionalities of a maya skin cluster object - - @type mesh_name: str - @param mesh_name: The name of the mesh - - @rtype: List[List[Union[int, float]]] - @returns: A list of list of weight indices and the weight values - """ - - mesh = Maya.get_element(mesh_name) - components = MGlobal.getSelectionListByName(f"{mesh_name}.vtx[*]").getComponent( - 0 - )[1] - weights_data, chunk = skin_cluster.getWeights(mesh, components) - iterator = [ - weights_data[i : i + chunk] for i in range(0, len(weights_data), chunk) - ] - - vertices_info = [] - for weights in iterator: - vertex_weights: List[float] = [] - vertices_info.append(vertex_weights) - - for i, weight in enumerate(weights): - if weight: - vertex_weights.append(i) - vertex_weights.append(weight) - return vertices_info - - -def get_skin_weights_data(mesh_name: str) -> Tuple[MFnMesh, MFnSkinCluster]: - """ - Gets the maya objects that manipulate the mesh node and the skin cluster for a given mesh name. - - @type mesh_name: str - @param mesh_name: The name of the mesh - - @rtype: Tuple[MFnMesh, MFnSkinCluster] - @returns: The maya object that manipulate the mesh node and the skin cluster for a given mesh name. - """ - - skin_cluster_name = mel.eval(f"findRelatedSkinCluster {mesh_name}") - if skin_cluster_name: - skin_cluster = MFnSkinCluster(Maya.get_element(skin_cluster_name)) - mesh_node = MFnMesh(Maya.get_element(mesh_name)) - return mesh_node, skin_cluster - raise DNAViewerError(f"Unable to find skin for given mesh: {mesh_name}") - - -def get_skin_weights_from_scene(mesh_name: str) -> MayaSkinWeights: - """ - Gets the instance of this class filled with data from the scene for a given mesh name. - - @type mesh_name: str - @param mesh_name: The mesh name - - @rtype: MayaSkinWeights - @returns: An instance of this class with the data from the scene - """ - - _, skin_cluster = get_skin_weights_data(mesh_name) - - return MayaSkinWeights(skin_cluster, mesh_name) - - -def get_file_joint_mappings( - skin_weights: MayaSkinWeights, skin_cluster: MFnSkinCluster -) -> List[int]: - """ - Returns a list of object indices representing the influences concerning the joint names specified in the skin weight model. - - @type skin_weights: MayaSkinWeights - @param skin_weights: The instance of the model storing data about skin weights - - @type skin_cluster: MFnSkinCluster - @param skin_cluster: An object for working with functions concerning a skin cluster in maya - - @rtype: List[int] - @returns: a list of indices representing the influences concerning the given joints - """ - - file_joint_mapping: List[int] = [] - for joint_name in skin_weights.joints: - file_joint_mapping.append( - skin_cluster.indexForInfluenceObject(Maya.get_element(joint_name)) - ) - return file_joint_mapping - - -def set_skin_weights_to_scene(mesh_name: str, skin_weights: MayaSkinWeights) -> None: - """ - Sets the skin weights to the scene. - - @type mesh_name: str - @param mesh_name: The mesh name - - @type skin_weights: MayaSkinWeights - @param skin_weights: The object containing data that need to be set to the scene. - """ - - mesh_node, skin_cluster = get_skin_weights_data(mesh_name) - - file_joint_mapping = get_file_joint_mappings(skin_weights, skin_cluster) - - import_skin_weights(skin_cluster, mesh_node, skin_weights, file_joint_mapping) - - logging.info("Set skin weights ended.") - - -def import_skin_weights( - skin_cluster: MFnSkinCluster, - mesh_node: MFnMesh, - skin_weights: MayaSkinWeights, - file_joint_mapping: List[int], -) -> None: - """ - Imports the skin weights to the scene using the joint mapping and the data provided in the model containing the weights. - - @type skin_cluster: MFnSkinCluster - @param skin_cluster: An object for working with functions concerning a skin cluster in maya - - @type mesh_node: MFnMesh - @param mesh_node: An object for working with functions concerning meshes in maya - - @type skin_weights: MayaSkinWeights - @param skin_weights: The instance of the model storing data about skin weights - - @type file_joint_mapping: List[int] - @param file_joint_mapping: a list of indices representing the influences concerning joints - """ - - temp_str = f"{skin_cluster.name()}.wl[" - for vtx_id in range(cmds.polyEvaluate(mesh_node.name(), vertex=True)): - vtx_info = skin_weights.vertices_info[vtx_id] - - vtx_str = f"{temp_str}{str(vtx_id)}].w[" - - cmds.setAttr(f"{vtx_str}0]", 0.0) - - for i in range(0, len(vtx_info), 2): - cmds.setAttr( - f"{vtx_str}{str(file_joint_mapping[int(vtx_info[i])])}]", - vtx_info[i + 1], - ) diff --git a/scripts/builder/maya/util.py b/scripts/builder/maya/util.py deleted file mode 100644 index 1a707c3..0000000 --- a/scripts/builder/maya/util.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from typing import Union - -from maya.api.OpenMaya import ( - MDagPath, - MFnDagNode, - MFnTransform, - MGlobal, - MSpace, - MVector, -) - -from ...common import DNAViewerError - - -class Maya: - """A utility class used for interfacing with maya transforms""" - - @staticmethod - def get_element(name: str) -> Union[MDagPath, MFnDagNode]: - """gets the Union[MDagPath, MFnDagNode] object of the element with the given name - - @type name: str - @param name: The name of the element to be retrieved - - @rtype: Union[MDagPath, MFnDagNode] - @returns: A OpenMaya object representing the given element - """ - try: - sellist = MGlobal.getSelectionListByName(name) - except Exception as exception: - raise DNAViewerError(f"Element with name:{name} not found!") from exception - - try: - return sellist.getDagPath(0) - except Exception: - return sellist.getDependNode(0) - - @staticmethod - def get_transform(name: str) -> MFnTransform: - """gets the transform of the element with the given name - - @type element: str - @param element: The element name that we want the transform of - - @rtype: MFnTransform - @returns: A MFnTransform object representing the given elements transform - """ - return MFnTransform(Maya.get_element(name)) - - @staticmethod - def get_translation(element: str, space: int = MSpace.kObject) -> MVector: - """gets the translation of the element with the given name - - @type element: str - @param element: The element name that we want the translation of - - @type space: str - @param space: A string value representing the translation space (default is "world") - - @rtype: MVector - @returns: A MVector object representing the given elements translation - """ - return MFnTransform(Maya.get_element(element)).translation(space) - - @staticmethod - def set_translation( - element: str, translation: MVector, space: int = MSpace.kObject - ) -> None: - """sets the translation of the element with the given name - - @type element: str - @param element: The element name that we want to set the translation of - - @type translation: MVector - @param translation: The new translation value - - @type space: str - @param space: A string value representing the translation space (default is "object") - """ - element_obj = Maya.get_transform(element) - element_obj.setTranslation(translation, space) diff --git a/scripts/builder/mesh.py b/scripts/builder/mesh.py deleted file mode 100644 index 5d21eca..0000000 --- a/scripts/builder/mesh.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from typing import List - -from ..builder.maya.mesh import MayaMesh -from ..dnalib.dnalib import DNA -from .config import Config - - -class Mesh: - """ - A builder class used for adding joints to the scene - - Attributes - ---------- - @type dna: DNA - @param dna: The location of the DNA file - - @type mesh_index: int - @param mesh_index: The mesh index we are working with - - @type joint_ids: List[int] - @param joint_ids: The joint indices used for adding skin - - @type joint_names: List[str] - @param joint_names: The joint names used for adding skin - - @type config: Config - @param config: The build options that will be applied when creating the mesh - - - @type mesh: MayaMesh - @param mesh: The builder class object for creating the meshes - - @type dna: DNA - @param dna: The DNA object that was loaded in - """ - - def __init__( - self, - config: Config, - dna: DNA, - mesh_index: int, - ) -> None: - self.mesh_index: int = mesh_index - self.joint_ids: List[int] = [] - self.joint_names: List[str] = [] - self.config = config - self.dna = dna - self.mesh = MayaMesh( - self.mesh_index, - self.dna, - blend_shape_group_prefix=self.config.blend_shape_group_prefix, - blend_shape_name_postfix=self.config.blend_shape_name_postfix, - skin_cluster_suffix=self.config.skin_cluster_suffix, - ) - - def build(self) -> None: - """Starts the build process, creates the neutral mesh, then adds normals, blends shapes and skin if needed""" - - self.create_neutral_mesh() - self.add_blend_shapes() - self.add_skin_cluster() - - def create_neutral_mesh(self) -> None: - """Creates the neutral mesh""" - - self.mesh.create_neutral_mesh() - - def add_blend_shapes(self) -> None: - """Reads in the blend shapes, then adds them to the mesh if it is set in the build options""" - - if self.config.add_blend_shapes: - logging.info("adding blend shapes...") - self.mesh.add_blend_shapes( - self.config.add_mesh_name_to_blend_shape_channel_name - ) - - def add_skin_cluster(self) -> None: - """Adds skin cluster to the mesh if it is set in the build options""" - - if self.config.add_skin_cluster and self.config.add_joints: - self.prepare_joints() - if self.joint_names: - self.mesh.add_skin_cluster(self.joint_names, self.joint_ids) - - def prepare_joints(self) -> None: - """ - Gets the joint indices and names needed for the given mesh. - """ - - self.prepare_joint_ids() - - joints = self.dna.read_all_neutral_joints() - self.joint_names = [] - for joint_id in self.joint_ids: - self.joint_names.append(joints[joint_id].name) - - def prepare_joint_ids(self) -> None: - joints_temp: List[int] = [] - joint_indices = self.dna.get_all_skin_weights_joint_indices_for_mesh( - self.mesh_index - ) - self.joint_ids = [] - if any(joint_indices): - for row in joint_indices: - for column in row: - joints_temp.append(column) - - self.joint_ids = list(set(joints_temp)) - self.joint_ids.sort() - else: - lod = self.dna.get_lowest_lod_containing_meshes([self.mesh_index]) - if lod: - self.joint_ids = self.dna.get_joint_indices_for_lod(lod) diff --git a/scripts/builder/rig_builder.py b/scripts/builder/rig_builder.py deleted file mode 100644 index 6c49997..0000000 --- a/scripts/builder/rig_builder.py +++ /dev/null @@ -1,293 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from importlib.machinery import SourceFileLoader -from importlib.util import module_from_spec, spec_from_loader -from pathlib import Path -from types import ModuleType -from typing import Optional - -from maya import cmds, mel -from maya.api.OpenMaya import MSpace, MVector - -from ..builder.maya.util import Maya -from ..common import ANALOG_GUI_HOLDER, GUI_HOLDER, RIG_LOGIC_PREFIX, DNAViewerError -from ..dnalib.dnalib import DNA -from .builder import Builder -from .config import RigConfig - - -class RigBuilder(Builder): - """ - A builder class used for building meshes - """ - - def __init__(self, dna: DNA, config: Optional[RigConfig] = None) -> None: - super().__init__(dna=dna, config=config) - self.config: Optional[RigConfig] - self.eye_l_pos: MVector - self.eye_r_pos: MVector - - def _build(self) -> None: - if super()._build(): - self.add_gui() - self.add_analog_gui() - self.add_rig_logic() - self.run_additional_assemble_script() - - def run_additional_assemble_script(self) -> None: - """ - Runs an additional assemble script if specified in the character configuration. - """ - - if self.config.aas_path: - logging.info("running additional assemble script...") - try: - module_name = Path(self.config.aas_path).stem - script = self.source_py_file(module_name, self.config.aas_path) - script_method = getattr(script, self.config.aas_method) - script_method( - self.config.get_top_level_group(), - self.config.get_rig_group(), - self.config.aas_parameter, - ) - except Exception as e: - raise DNAViewerError(f"Can't run aas script. Reason: {e}") from e - - def add_rig_logic(self) -> None: - """ - Creates and adds a rig logic node specified in the character configuration. - """ - - if ( - self.config.add_rig_logic - and self.config.add_joints - and self.config.add_skin_cluster - and self.config.add_blend_shapes - and self.config.aas_path - and self.config.analog_gui_path - and self.config.gui_path - ): - logging.info("adding rig logic...") - try: - cmds.loadPlugin("embeddedRL4.mll") - self.config.rig_logic_name = f"{RIG_LOGIC_PREFIX}{self.dna.name}" - dna = self.dna.path.replace("\\", "/") - - mel_command = self.config.rig_logic_command - mel_command += f' -n "{self.config.rig_logic_name}"' - mel_command += f' -dfp "{dna}"' - mel_command += f' -cn "{self.config.control_naming}"' - mel_command += f' -jn "{self.config.joint_naming}"' - mel_command += f' -bsn "{self.config.blend_shape_naming}"' - mel_command += f' -amn "{self.config.animated_map_naming}"; ' - - logging.info(f"mel command: {mel_command}") - mel.eval(mel_command) - except Exception as e: - logging.error( - "The procedure needed for assembling the rig logic was not found, the plugin needed for this might not be loaded." - ) - raise DNAViewerError( - f"Something went wrong, skipping adding the rig logic... Reason: {e}" - ) from e - - def add_gui(self) -> None: - """ - Adds a gui according to the specified gui options. If none is specified no gui will be added. - """ - - if self.config.gui_path: - logging.info("adding gui...") - - self.import_gui( - gui_path=self.config.gui_path, - group_name=GUI_HOLDER, - ) - self.position_gui(GUI_HOLDER) - - self.add_ctrl_attributes() - self.add_animated_map_attributes() - - def add_ctrl_attributes(self) -> None: - """ - Adds and sets the raw gui control attributes. - """ - - gui_control_names = self.dna.get_raw_control_names() - for name in gui_control_names: - ctrl_and_attr_names = name.split(".") - self.add_attribute( - control_name=ctrl_and_attr_names[0], - long_name=ctrl_and_attr_names[1], - ) - - def add_animated_map_attributes(self) -> None: - """ - Adds and sets the animated map attributes. - """ - - names = self.dna.get_animated_map_names() - for name in names: - long_name = name.replace(".", "_") - if self.config.gui_path: - self.add_attribute( - control_name=self.config.animated_map_attribute_multipliers_name, - long_name=long_name, - ) - - def position_gui(self, group_name: str) -> None: - """Sets the gui position to align with the character eyes""" - - if not cmds.objExists(self.config.eye_gui_name) or not cmds.objExists( - self.config.left_eye_joint_name - ): - logging.warning( - "could not find joints needed for positioning the gui, leaving it at its default position..." - ) - return - - gui_y = ( - Maya.get_transform(self.config.eye_gui_name).translation(MSpace.kObject).y - ) - eyes_y = ( - Maya.get_transform(self.config.left_eye_joint_name) - .translation(MSpace.kObject) - .y - ) - delta_y = eyes_y - gui_y - - if isinstance(self.config.gui_translate_x, str): - try: - logging.warning( - "gui_translate_x should be a float, trying to cast the value to float..." - ) - self.config.gui_translate_x = float(self.config.gui_translate_x) - except ValueError: - logging.error("could not cast string value to float") - return - - Maya.get_transform(group_name).translateBy( - MVector(self.config.gui_translate_x, delta_y, 0), MSpace.kObject - ) - - def add_analog_gui(self) -> None: - """ - Adds an analog gui according to the specified analog gui options. If none is specified no analog gui will be - added. - """ - - if self.config.analog_gui_path and self.config.add_joints: - logging.info("adding analog gui...") - self.import_gui( - gui_path=self.config.analog_gui_path, - group_name=ANALOG_GUI_HOLDER, - ) - if self.dna.joints.names: - self.add_eyes() - self.add_eye_locators() - - def add_eyes(self) -> None: - """Add eyes to the analog gui""" - - self.eye_l_pos = Maya.get_translation(self.config.left_eye_joint_name) - self.eye_r_pos = Maya.get_translation(self.config.right_eye_joint_name) - - Maya.set_translation( - self.config.central_driver_name, - Maya.get_translation(self.config.facial_root_joint_name), - ) - - delta_l = Maya.get_translation( - self.config.left_eye_aim_up_name - ) - Maya.get_translation(self.config.left_eye_driver_name) - delta_r = Maya.get_translation( - self.config.right_eye_aim_up_name - ) - Maya.get_translation(self.config.right_eye_driver_name) - - Maya.set_translation(self.config.left_eye_driver_name, self.eye_l_pos) - Maya.set_translation( - self.config.right_eye_driver_name, - self.eye_r_pos, - ) - Maya.set_translation( - self.config.left_eye_aim_up_name, - MVector( - self.eye_l_pos[0] + delta_l[0], - self.eye_l_pos[1] + delta_l[1], - self.eye_l_pos[2] + delta_l[2], - ), - ) - Maya.set_translation( - self.config.right_eye_aim_up_name, - MVector( - self.eye_r_pos[0] + delta_r[0], - self.eye_r_pos[1] + delta_r[1], - self.eye_r_pos[2] + delta_r[2], - ), - ) - - def add_eye_locators(self) -> None: - """Add eye locators to the analog gui""" - - eye_l_locator_pos = Maya.get_translation(self.config.le_aim) - eye_r_locator_pos = Maya.get_translation(self.config.re_aim) - central_aim_pos = Maya.get_translation(self.config.central_aim) - - eye_middle_delta = (self.eye_l_pos - self.eye_r_pos) / 2 - - eye_middle = self.eye_r_pos + eye_middle_delta - - Maya.set_translation( - self.config.central_aim, - MVector(eye_middle[0], eye_middle[1], central_aim_pos[2]), - ) - Maya.set_translation( - self.config.le_aim, - MVector(self.eye_l_pos[0], self.eye_l_pos[1], eye_l_locator_pos[2]), - ) - Maya.set_translation( - self.config.re_aim, - MVector(self.eye_r_pos[0], self.eye_r_pos[1], eye_r_locator_pos[2]), - ) - - def source_py_file(self, name: str, path: str) -> Optional[ModuleType]: - """ - Used for loading a python file, used for additional assemble script. - - @type name: str - @param name: The name of the module. - - @type path: str - @param path: The path of the python file. - - @rtype: Optional[ModuleType] - @returns: The loaded module. - """ - - path_obj = Path(path.strip()) - if ( - path - and path_obj.exists() - and path_obj.is_file() - and path_obj.suffix == ".py" - ): - spec = spec_from_loader(name, SourceFileLoader(name, path)) - module = module_from_spec(spec) - spec.loader.exec_module(module) - return module - raise DNAViewerError(f"File {path} is not found!") - - def import_gui(self, gui_path: str, group_name: str) -> None: - """ - Imports a gui using the provided parameters. - - @type gui_path: str - @param gui_path: The path of the gui file that needs to be imported. - - @type group_name: str - @param group_name: The name of the transform that holds the imported asset. - """ - - cmds.file(gui_path, i=True, groupReference=True, groupName=group_name) diff --git a/scripts/dnalib/__init__.py b/scripts/dnalib/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/dnalib/behavior.py b/scripts/dnalib/behavior.py deleted file mode 100644 index d824890..0000000 --- a/scripts/dnalib/behavior.py +++ /dev/null @@ -1,371 +0,0 @@ -from dataclasses import dataclass, field -from typing import List, Optional, cast - -from dna import BinaryStreamReader as DNAReader - -from .definition import Definition -from .layer import Layer - - -class Behavior(Definition): - """ - @type reader: BinaryStreamReader - @param reader: The binary stream reader being used - - @type gui_to_raw: ConditionalTable - @param gui_to_raw: Mapping data about gui to raw values - - @type psd: PSDMatrix - @param psd: The data representing Pose Space Deformation - - @type blend_shapes: BlendShapesData - @param blend_shapes: The data representing blend shapes - - @type animated_maps: AnimatedMapsConditionalTable - @param animated_maps: The data representing animated maps - - @type joints: JointGroups - @param joints: The data representing joints - """ - - def __init__(self, reader: DNAReader, layers: Optional[List[Layer]]) -> None: - super().__init__(reader, layers) - - self.gui_to_raw = ConditionalTable() - self.psd = PSDMatrix() - self.blend_shapes = BlendShapesData() - self.animated_maps_conditional_table = AnimatedMapsConditionalTable() - self.joint_groups = JointGroups() - self.behavior_read = False - - def start_read(self) -> None: - super().start_read() - self.behavior_read = False - - def is_read(self) -> bool: - return super().is_read() and self.behavior_read - - def read(self) -> None: - """ - Starts reading in the behavior part of the DNA - """ - super().read() - - if not self.behavior_read and self.layer_enabled(Layer.behavior): - self.behavior_read = True - self.add_gui_to_raw() - self.add_psd() - self.add_joint_groups() - self.add_blend_shapes() - self.add_animated_maps_conditional_table() - - def get_animated_map_lods(self) -> List[int]: - return cast(List[int], self.reader.getAnimatedMapLODs()) - - def get_animated_map_from_values(self) -> List[float]: - return cast(List[float], self.reader.getAnimatedMapFromValues()) - - def get_animated_map_to_values(self) -> List[float]: - return cast(List[float], self.reader.getAnimatedMapToValues()) - - def get_animated_map_slope_values(self) -> List[float]: - return cast(List[float], self.reader.getAnimatedMapSlopeValues()) - - def get_animated_map_cut_values(self) -> List[float]: - return cast(List[float], self.reader.getAnimatedMapCutValues()) - - def get_animated_map_input_indices(self) -> List[int]: - return cast(List[int], self.reader.getAnimatedMapInputIndices()) - - def get_animated_map_output_indices(self) -> List[int]: - return cast(List[int], self.reader.getAnimatedMapOutputIndices()) - - def get_gui_to_raw_from_values(self) -> List[float]: - return cast(List[float], self.reader.getGUIToRawFromValues()) - - def get_gui_to_raw_to_values(self) -> List[float]: - return cast(List[float], self.reader.getGUIToRawToValues()) - - def gget_gui_to_raw_slope_values(self) -> List[float]: - return cast(List[float], self.reader.getGUIToRawSlopeValues()) - - def get_gui_to_raw_cut_values(self) -> List[float]: - return cast(List[float], self.reader.getGUIToRawCutValues()) - - def get_gui_to_raw_input_indices(self) -> List[int]: - return cast(List[int], self.reader.getGUIToRawInputIndices()) - - def get_gui_to_raw_output_indices(self) -> List[int]: - return cast(List[int], self.reader.getGUIToRawOutputIndices()) - - def get_psd_count(self) -> int: - return cast(int, self.reader.getPSDCount()) - - def get_psd_row_indices(self) -> List[int]: - return cast(List[int], self.reader.getPSDRowIndices()) - - def get_psd_column_indices(self) -> List[int]: - return cast(List[int], self.reader.getPSDColumnIndices()) - - def get_psd_values(self) -> List[float]: - return cast(List[float], self.reader.getPSDValues()) - - def get_blend_shape_channel_lods(self) -> List[int]: - return cast(List[int], self.reader.getBlendShapeChannelLODs()) - - def get_blend_shape_channel_input_indices(self) -> List[int]: - return cast(List[int], self.reader.getBlendShapeChannelInputIndices()) - - def get_blend_shape_channel_output_indices(self) -> List[int]: - return cast(List[int], self.reader.getBlendShapeChannelOutputIndices()) - - def get_joint_row_count(self) -> int: - return cast(int, self.reader.getJointRowCount()) - - def get_joint_column_count(self) -> int: - return cast(int, self.reader.getJointColumnCount()) - - def get_joint_variable_attribute_indices(self) -> int: - return cast(int, self.reader.getJointVariableAttributeIndices()) - - def get_joint_group_count(self) -> int: - return cast(int, self.reader.getJointGroupCount()) - - def get_joint_group_logs(self, joint_group_index: int) -> List[int]: - return cast(List[int], self.reader.getJointGroupLODs(joint_group_index)) - - def get_joint_group_input_indices(self, joint_group_index: int) -> List[int]: - return cast(List[int], self.reader.getJointGroupInputIndices(joint_group_index)) - - def get_joint_group_output_indices(self, joint_group_index: int) -> List[int]: - return cast( - List[int], self.reader.getJointGroupOutputIndices(joint_group_index) - ) - - def get_joint_group_values(self, joint_group_index: int) -> List[float]: - return cast(List[float], self.reader.getJointGroupValues(joint_group_index)) - - def get_joint_group_joint_indices(self, joint_group_index: int) -> List[int]: - return cast(List[int], self.reader.getJointGroupJointIndices(joint_group_index)) - - def add_gui_to_raw(self) -> None: - """Reads in the gui to raw mapping""" - - self.reader.gui_to_raw = ConditionalTable( - inputs=self.get_gui_to_raw_input_indices(), - outputs=self.get_gui_to_raw_output_indices(), - from_values=self.get_gui_to_raw_from_values(), - to_values=self.get_gui_to_raw_to_values(), - slope_values=self.gget_gui_to_raw_slope_values(), - cut_values=self.get_gui_to_raw_cut_values(), - ) - - def add_psd(self) -> None: - """Reads in the PSD part of the behavior""" - - self.psd = PSDMatrix( - count=self.get_psd_count(), - rows=self.get_psd_row_indices(), - columns=self.get_psd_column_indices(), - values=self.get_psd_values(), - ) - - def add_joint_groups(self) -> None: - """Reads in the joints part of the behavior""" - - self.joint_groups.joint_row_count = self.reader.getJointRowCount() - self.joint_groups.joint_column_count = self.reader.getJointColumnCount() - for lod in range(self.get_lod_count()): - self.joint_groups.joint_variable_attribute_indices.append( - self.reader.getJointVariableAttributeIndices(lod) - ) - for joint_group_index in range(self.get_joint_group_count()): - self.joint_groups.joint_groups.append( - JointGroup( - lods=self.get_joint_group_logs(joint_group_index), - inputs=self.get_joint_group_input_indices(joint_group_index), - outputs=self.get_joint_group_output_indices(joint_group_index), - values=self.get_joint_group_values(joint_group_index), - joints=self.get_joint_group_joint_indices(joint_group_index), - ) - ) - - def add_blend_shapes(self) -> None: - """Reads in the blend shapes part of the behavior""" - - self.blend_shapes = BlendShapesData( - lods=self.get_blend_shape_channel_lods(), - inputs=self.get_blend_shape_channel_input_indices(), - outputs=self.get_blend_shape_channel_output_indices(), - ) - - def add_animated_maps_conditional_table(self) -> None: - """Reads in the animated maps part of the behavior""" - - self.reader.animated_maps_conditional_table = AnimatedMapsConditionalTable( - lods=self.get_animated_map_lods(), - conditional_table=ConditionalTable( - from_values=self.get_animated_map_from_values(), - to_values=self.get_animated_map_to_values(), - slope_values=self.get_animated_map_slope_values(), - cut_values=self.get_animated_map_cut_values(), - inputs=self.get_animated_map_input_indices(), - outputs=self.get_animated_map_output_indices(), - ), - ) - - -@dataclass -class ConditionalTable: - """ - A model class for holding various values - - Attributes - ---------- - @type from_values: List[float] - @param from_values: The list of values - - @type to_values: List[float] - @param to_values: The list of values - - @type slope_values: List[float] - @param slope_values: The list of slope values - - @type cut_values: List[float] - @param cut_values: The list of cut values - - @type inputs: List[int] - @param inputs: The indices of inputs - - @type outputs: List[int] - @param outputs: The indices of outputs - """ - - from_values: List[float] = field(default_factory=list) - to_values: List[float] = field(default_factory=list) - slope_values: List[float] = field(default_factory=list) - cut_values: List[float] = field(default_factory=list) - inputs: List[int] = field(default_factory=list) - outputs: List[int] = field(default_factory=list) - - -@dataclass -class PSDMatrix: - """ - A model class for holding data about Pose Space Deformation - - Attributes - ---------- - @type count: int - @param count: The list of values - - @type rows: List[int] - @param rows: List of row indices used for storing values - - @type columns: List[int] - @param columns: List of row indices used for storing values - - @type values: List[float] - @param values: The list of values, that can be accessed from the row and column index - """ - - count: Optional[int] = field(default=None) - rows: List[int] = field(default_factory=list) - columns: List[int] = field(default_factory=list) - values: List[float] = field(default_factory=list) - - -@dataclass -class JointGroup: - """ - A model class for holding data about joint groups - - Attributes - ---------- - @type lods: List[int] - @param lods: A list of lod indices that the joint group is contained within - - @type values: List[float] - @param values: A list of values - - @type joints: List[int] - @param joints: A list of joint indices - - @type inputs: List[int] - @param inputs: The indices of inputs - - @type outputs: List[int] - @param outputs: The indices of outputs - """ - - lods: List[int] = field(default_factory=list) - values: List[float] = field(default_factory=list) - joints: List[int] = field(default_factory=list) - inputs: List[int] = field(default_factory=list) - outputs: List[int] = field(default_factory=list) - - -@dataclass -class BlendShapesData: - """ - A model class for holding data about blend shapes - - Attributes - ---------- - @type lods: List[int] - @param lods: A list of lod indices that the blend shapes are contained within - - @type inputs: List[int] - @param inputs: The indices of inputs - - @type outputs: List[int] - @param outputs: The indices of outputs - """ - - lods: List[int] = field(default_factory=list) - inputs: List[int] = field(default_factory=list) - outputs: List[int] = field(default_factory=list) - - -@dataclass -class AnimatedMapsConditionalTable: - """ - A model class for holding data about animated maps - - Attributes - ---------- - @type lods: List[int] - @param lods: A list of lod indices that the blend shapes are contained within - - @type conditional_table: ConditionalTable - @param conditional_table: Data needed for animated maps - """ - - lods: List[int] = field(default_factory=list) - conditional_table: ConditionalTable = field(default_factory=ConditionalTable) - - -@dataclass -class JointGroups: - """ - A model class for storing data about joints - - Attributes - ---------- - @type joint_row_count: int - @param joint_row_count: The row count of the matrix that stores the joints data - - @type joint_column_count: int - @param joint_column_count: The column count of the matrix that stores the joints data - - @type joint_variable_attribute_indices: List[List[int]] - @param joint_variable_attribute_indices: List of joint variable attribute indices per LOD - - @type joint_groups: List[JointGroup] - @param joint_groups: The list of joint groups - """ - - joint_row_count: Optional[int] = field(default=None) - joint_column_count: Optional[int] = field(default=None) - joint_variable_attribute_indices: List[List[int]] = field(default_factory=list) - joint_groups: List[JointGroup] = field(default_factory=list) diff --git a/scripts/dnalib/definition.py b/scripts/dnalib/definition.py deleted file mode 100644 index b64aa99..0000000 --- a/scripts/dnalib/definition.py +++ /dev/null @@ -1,333 +0,0 @@ -from dataclasses import dataclass, field -from typing import Dict, List, Optional, Tuple, cast - -from dna import BinaryStreamReader as DNAReader -from dna import MeshBlendShapeChannelMapping - -from ..model import Point3 -from .descriptor import Descriptor -from .layer import Layer - - -class Definition(Descriptor): - """ - A class used for reading and accessing the definition part of the DNA file - - Attributes - ---------- - @type reader: BinaryStreamReader - @param reader: The binary stream reader being used - - @type definition: DefinitionModel - @param definition: The object that holds the definition data read from the DNA file - - @type joints: Joints - @param joints: The data about joints - - @type blend_shape_channels: GeometryEntity - @param blend_shape_channels: The names and indices of blend shape channels - - @type animated_maps: GeometryEntity - @param animated_maps: The names and indices of animated maps - - @type meshes: GeometryEntity - @param meshes: The names and indices of the meshes - - @type gui_control_names: List[str] - @param gui_control_names: The list of gui control names - - @type raw_control_names: List[str] - @param raw_control_names: The list of raw control names - - @type mesh_blend_shape_channel_mapping: List[Tuple[int, int]] - @param mesh_blend_shape_channel_mapping: Mapping of mesh index to the blend shape channel index - - @type mesh_blend_shape_channel_mapping_indices_for_lod: List[List[int]] - @param mesh_blend_shape_channel_mapping_indices_for_lod: The list of blend shape channel mapping indices by lod - - @type neutral_joint_translations: List[Point3] - @param neutral_joint_translations: The list of neutral joint translations - - @type neutral_joint_rotations: List[Point3] - @param neutral_joint_rotations: The list of neutral joint rotations - """ - - def __init__(self, reader: DNAReader, layers: Optional[List[Layer]]) -> None: - super().__init__(reader, layers) - self.joints = Joints() - self.blend_shape_channels = GeometryEntity() - self.animated_maps = GeometryEntity() - self.meshes = GeometryEntity() - self.meshes_mapping: Dict[str, int] = {} - - self.gui_control_names: List[str] = [] - self.raw_control_names: List[str] = [] - - self.mesh_blend_shape_channel_mapping: List[Tuple[int, int]] = [] - self.mesh_blend_shape_channel_mapping_indices_for_lod: List[List[int]] = [] - - self.neutral_joint_translations: List[Point3] = [] - self.neutral_joint_rotations: List[Point3] = [] - self.definition_read = False - - def start_read(self) -> None: - super().start_read() - self.definition_read = False - - def is_read(self) -> bool: - return super().is_read() and self.definition_read - - def read(self) -> None: - """ - Starts reading in the definition part of the DNA - - @rtype: DefinitionModel - @returns: the instance of the created definition model - """ - super().read() - - if not self.definition_read and self.layer_enabled(Layer.definition): - self.definition_read = True - self.add_controls() - self.add_joints() - self.add_blend_shape_channels() - self.add_animated_maps() - self.add_meshes() - self.add_mesh_blend_shape_channel_mapping() - self.add_neutral_joints() - - def get_lod_count(self) -> int: - return cast(int, self.reader.getLODCount()) - - def get_gui_control_count(self) -> int: - return cast(int, self.reader.getGUIControlCount()) - - def get_gui_control_name(self, index: int) -> str: - return cast(str, self.reader.getGUIControlName(index)) - - def get_raw_control_count(self) -> int: - return cast(int, self.reader.getRawControlCount()) - - def get_raw_control_name(self, index: int) -> str: - return cast(str, self.reader.getRawControlName(index)) - - def get_raw_control_names(self) -> List[str]: - names = [] - for i in range(self.get_raw_control_count()): - names.append(self.get_raw_control_name(i)) - return names - - def get_neutral_joint_translation(self, index: int) -> Point3: - translation = cast(List[float], self.reader.getNeutralJointTranslation(index)) - return Point3(translation[0], translation[1], translation[2]) - - def get_neutral_joint_translation_xs(self) -> List[float]: - return cast(List[float], self.reader.getNeutralJointTranslationXs()) - - def get_neutral_joint_translation_ys(self) -> List[float]: - return cast(List[float], self.reader.getNeutralJointTranslationYs()) - - def get_neutral_joint_translation_zs(self) -> List[float]: - return cast(List[float], self.reader.getNeutralJointTranslationZs()) - - def get_neutral_joint_rotation(self, index: int) -> Point3: - translation = cast(List[float], self.reader.getNeutralJointRotation(index)) - return Point3(translation[0], translation[1], translation[2]) - - def get_neutral_joint_rotation_xs(self) -> List[float]: - return cast(List[float], self.reader.getNeutralJointRotationXs()) - - def get_neutral_joint_rotation_ys(self) -> List[float]: - return cast(List[float], self.reader.getNeutralJointRotationYs()) - - def get_neutral_joint_rotation_zs(self) -> List[float]: - return cast(List[float], self.reader.getNeutralJointRotationZs()) - - def get_mesh_blend_shape_channel_mapping_count(self) -> int: - return cast(int, self.reader.getMeshBlendShapeChannelMappingCount()) - - def get_mesh_blend_shape_channel_mapping( - self, index: int - ) -> MeshBlendShapeChannelMapping: - return cast( - MeshBlendShapeChannelMapping, - self.reader.getMeshBlendShapeChannelMapping(index), - ) - - def get_mesh_blend_shape_channel_mapping_for_lod(self, lod: int) -> List[int]: - return cast( - List[int], self.reader.getMeshBlendShapeChannelMappingIndicesForLOD(lod) - ) - - def get_joint_count(self) -> int: - return cast(int, self.reader.getJointCount()) - - def get_joint_name(self, index: int) -> str: - return cast(str, self.reader.getJointName(index)) - - def get_joint_parent_index(self, index: int) -> int: - return cast(int, self.reader.getJointParentIndex(index)) - - def get_joint_indices_for_lod(self, index: int) -> List[int]: - return cast(List[int], self.reader.getJointIndicesForLOD(index)) - - def get_blend_shape_channel_count(self) -> int: - return cast(int, self.reader.getBlendShapeChannelCount()) - - def get_blend_shape_channel_name(self, index: int) -> str: - return cast(str, self.reader.getBlendShapeChannelName(index)) - - def get_mesh_count(self) -> int: - return cast(int, self.reader.getMeshCount()) - - def get_mesh_name(self, index: int) -> str: - return cast(str, self.reader.getMeshName(index)) - - def get_mesh_indices_for_lod(self, index: int) -> List[int]: - return cast(List[int], self.reader.getMeshIndicesForLOD(index)) - - def get_blend_shape_channel_indices_for_lod(self, index: int) -> List[int]: - return cast(List[int], self.reader.getBlendShapeChannelIndicesForLOD(index)) - - def get_animated_map_count(self) -> int: - return cast(int, self.reader.getAnimatedMapCount()) - - def get_animated_map_name(self, index: int) -> str: - return cast(str, self.reader.getAnimatedMapName(index)) - - def get_animated_map_names(self) -> List[str]: - names = [] - for i in range(self.get_animated_map_count()): - names.append(self.get_animated_map_name(i)) - return names - - def get_animated_map_indices_for_lod(self, index: int) -> List[int]: - return cast(List[int], self.reader.getAnimatedMapIndicesForLOD(index)) - - def get_translation_unit(self) -> int: - return cast(int, self.reader.getTranslationUnit()) - - def get_rotation_unit(self) -> int: - return cast(int, self.reader.getRotationUnit()) - - def add_neutral_joints(self) -> None: - """Reads in the neutral joints part of the definition""" - - neutral_joint_translation_xs = self.get_neutral_joint_translation_xs() - neutral_joint_translation_ys = self.get_neutral_joint_translation_ys() - neutral_joint_translation_zs = self.get_neutral_joint_translation_zs() - neutral_joint_translation_count_x = len(neutral_joint_translation_xs) - for index in range(neutral_joint_translation_count_x): - self.neutral_joint_translations.append( - Point3( - x=neutral_joint_translation_xs[index], - y=neutral_joint_translation_ys[index], - z=neutral_joint_translation_zs[index], - ) - ) - neutral_joint_rotation_xs = self.get_neutral_joint_rotation_xs() - neutral_joint_rotation_ys = self.get_neutral_joint_rotation_ys() - neutral_joint_rotation_zs = self.get_neutral_joint_rotation_zs() - neutral_joint_rotation_count_x = len(neutral_joint_rotation_xs) - for index in range(neutral_joint_rotation_count_x): - self.neutral_joint_rotations.append( - Point3( - x=neutral_joint_rotation_xs[index], - y=neutral_joint_rotation_ys[index], - z=neutral_joint_rotation_zs[index], - ) - ) - - def add_mesh_blend_shape_channel_mapping(self) -> None: - """Reads in the mesh blend shape channel mapping""" - - for index in range(self.get_mesh_blend_shape_channel_mapping_count()): - mapping = self.get_mesh_blend_shape_channel_mapping(index) - self.mesh_blend_shape_channel_mapping.append( - (mapping.meshIndex, mapping.blendShapeChannelIndex) - ) - for lod in range(self.get_lod_count()): - self.mesh_blend_shape_channel_mapping_indices_for_lod.append( - self.get_mesh_blend_shape_channel_mapping_for_lod(lod) - ) - - def add_meshes(self) -> None: - """Reads in the meshes of the definition""" - - for index in range(self.get_mesh_count()): - mesh_name = self.get_mesh_name(index) - self.meshes.names.append(mesh_name) - self.meshes_mapping[mesh_name] = index - for index in range(self.get_lod_count()): - self.meshes.lod_indices.append(self.get_mesh_indices_for_lod(index)) - - def add_animated_maps(self) -> None: - """Reads in the animated maps of the definition""" - - for index in range(self.get_animated_map_count()): - self.animated_maps.names.append(self.get_animated_map_name(index)) - for index in range(self.get_lod_count()): - self.animated_maps.lod_indices.append( - self.get_animated_map_indices_for_lod(index) - ) - - def add_blend_shape_channels(self) -> None: - """Reads in the neutral joints part of the definition""" - - for index in range(self.get_blend_shape_channel_count()): - self.blend_shape_channels.names.append( - self.get_blend_shape_channel_name(index) - ) - for index in range(self.get_lod_count()): - self.blend_shape_channels.lod_indices.append( - self.get_blend_shape_channel_indices_for_lod(index) - ) - - def add_joints(self) -> None: - """Reads in the joints of the definition""" - - for index in range(self.get_joint_count()): - self.joints.names.append(self.get_joint_name(index)) - self.joints.parent_index.append(self.get_joint_parent_index(index)) - for index in range(self.get_lod_count()): - self.joints.lod_indices.append(self.get_joint_indices_for_lod(index)) - - def add_controls(self) -> None: - """Reads in the gui and raw controls of the definition""" - - for index in range(self.get_gui_control_count()): - self.gui_control_names.append(self.get_gui_control_name(index)) - for index in range(self.get_raw_control_count()): - self.raw_control_names.append(self.get_raw_control_name(index)) - - -@dataclass -class GeometryEntity: - """ - A model class for holding names and indices - - Attributes - ---------- - @type names: List[str] - @param names: List of names - - @type lod_indices: List[List[int]] - @param lod_indices: List of indices per lod - """ - - names: List[str] = field(default_factory=list) - lod_indices: List[List[int]] = field(default_factory=list) - - -@dataclass -class Joints(GeometryEntity): - """ - A model class for holding data about the joints - - Attributes - ---------- - @type parent_index: List[int] - @param parent_index: List of parent indices for each joint index - """ - - parent_index: List[int] = field(default_factory=list) diff --git a/scripts/dnalib/descriptor.py b/scripts/dnalib/descriptor.py deleted file mode 100644 index c359702..0000000 --- a/scripts/dnalib/descriptor.py +++ /dev/null @@ -1,129 +0,0 @@ -from typing import Dict, List, Optional, Tuple - -from dna import BinaryStreamReader as DNAReader - -from ..dnalib.layer import Layer - - -class Descriptor: - """ - A class used for reading and accessing the descriptor part of the DNA file - - Attributes - ---------- - - @type name: str - @param name: The name of the character - - @type archetype: int - @param archetype: A value that represents the archetype of the character - - @type gender: int - @param gender: A value that represents the gender of the character - - @type age: int - @param age: The age of the character - - @type metadata: Dict[str, str] - @param metadata: Metadata stored for the character - - @type translation_unit: int - @param translation_unit: The translation unit that was used for creating the character - - @type rotation_unit: int - @param rotation_unit: The translation unit that was used for creating the character - - @type coordinate_system: Tuple[int, int, int] - @param coordinate_system: A tuple representing the coordinate system - - @type lod_count: int - @param lod_count: The number of LODs for the characters - - @type db_max_lod:int - @param db_max_lod: A LOD constraint representing the greatest LOD we wish wish to produce (ie. if the value is n, the potential LODs are 0, 1, .. n-1) - - @type db_complexity: str - @param db_complexity: Will be used in future - - @type db_name: str - @param db_name: DB identifier - """ - - def __init__(self, reader: DNAReader, layers: Optional[List[Layer]]) -> None: - self.reader = reader - self.layers = layers - self.name: Optional[str] = None - self.archetype: Optional[int] = None - self.gender: Optional[int] = None - self.age: Optional[int] = None - self.metadata: Dict[str, str] = {} - - self.translation_unit: Optional[int] = None - self.rotation_unit: Optional[int] = None - - self.coordinate_system: Optional[Tuple[int, int, int]] = None - - self.lod_count: Optional[int] = None - self.db_max_lod: Optional[int] = None - self.db_complexity: Optional[str] = None - self.db_name: Optional[str] = None - self.descriptor_read = False - - def start_read(self) -> None: - self.descriptor_read = False - - def is_read(self) -> bool: - return self.descriptor_read - - def layer_enabled(self, layer: Layer) -> bool: - return layer in self.layers or Layer.all in self.layers - - def read(self) -> None: - """ - Starts reading in the descriptor part of the DNA - - @rtype: DescriptorModel - @returns: the instance of the created descriptor model - """ - - if not self.descriptor_read and self.layer_enabled(Layer.descriptor): - self.descriptor_read = True - self.add_basic_data() - self.add_metadata() - self.add_geometry_data() - self.add_db_data() - - def add_basic_data(self) -> None: - """Reads in the character name, archetype, gender and age""" - - self.name = self.reader.getName() - self.archetype = self.reader.getArchetype() - self.gender = self.reader.getGender() - self.age = self.reader.getAge() - - def add_metadata(self) -> None: - """Reads in the metadata provided from the DNA file""" - - for i in range(self.reader.getMetaDataCount()): - key = self.reader.getMetaDataKey(i) - self.metadata[key] = self.reader.getMetaDataValue(key) - - def add_geometry_data(self) -> None: - """Sets the translation unit, rotation unit, and coordinate system from the DNA file""" - - self.translation_unit = self.reader.getTranslationUnit() - self.rotation_unit = self.reader.getRotationUnit() - coordinate_system = self.reader.getCoordinateSystem() - self.coordinate_system = ( - coordinate_system.xAxis, - coordinate_system.yAxis, - coordinate_system.zAxis, - ) - - def add_db_data(self) -> None: - """Reads in the db data from the DNA file""" - - self.lod_count = self.reader.getLODCount() - self.db_max_lod = self.reader.getDBMaxLOD() - self.db_complexity = self.reader.getDBComplexity() - self.db_name = self.reader.getDBName() diff --git a/scripts/dnalib/dnalib.py b/scripts/dnalib/dnalib.py deleted file mode 100644 index 27a1d0f..0000000 --- a/scripts/dnalib/dnalib.py +++ /dev/null @@ -1,250 +0,0 @@ -from typing import List, Optional, Tuple - -from dna import BinaryStreamReader as DNAReader -from dna import DataLayer_All, FileStream, Status - -from ..common import DNAViewerError -from ..model import UV, BlendShape, Joint, Layout, Point3 -from .behavior import Behavior -from .geometry import Geometry -from .layer import Layer - - -class DNA(Behavior, Geometry): - """ - A class used for accessing data in DNA file. - - @type dna_path: str - @param dna_path: The path of the DNA file - - @type layers: Optional[List[Layer]] - @param layers: List of parts of DNA to be loaded. If noting is passed, whole DNA is going to be loaded. Same as - passing Layer.all. - """ - - def __init__(self, dna_path: str, layers: Optional[List[Layer]] = None) -> None: - self.path = dna_path - self.reader = self.create_reader(dna_path) - layers = layers or [Layer.all] - Behavior.__init__(self, self.reader, layers) - Geometry.__init__(self, self.reader, layers) - self.read() - - def create_reader(self, dna_path: str) -> DNAReader: - """ - Creates a stream reader needed for reading values from the DNA file. - - @type dna_path: str - @param dna_path: The path of the DNA file - - @rtype: DNA - @returns: The reader needed for reading values from the DNA file - """ - - stream = FileStream( - dna_path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary - ) - - reader = DNAReader(stream, DataLayer_All) - reader.read() - if not Status.isOk(): - status = Status.get() - raise RuntimeError(f"Error loading DNA: {status.message}") - return reader - - def is_read(self) -> bool: - return Behavior.is_read(self) and Geometry.is_read(self) - - def read(self) -> None: - if not self.is_read(): - self.start_read() - Behavior.read(self) - Geometry.read(self) - - def read_all_neutral_joints(self) -> List[Joint]: - joints = [] - for i in range(self.get_joint_count()): - name = self.get_joint_name(i) - translation = self.get_neutral_joint_translation(i) - orientation = self.get_neutral_joint_rotation(i) - parent_name = self.get_joint_name(self.get_joint_parent_index(i)) - - joint = Joint( - name=name, - translation=translation, - orientation=orientation, - parent_name=parent_name, - ) - - joints.append(joint) - - return joints - - def get_all_skin_weights_joint_indices_for_mesh( - self, mesh_index: int - ) -> List[List[int]]: - return self.geometry_meshes[mesh_index].skin_weights.joint_indices - - def get_blend_shape_target_deltas_with_vertex_id( - self, mesh_index: int, blend_shape_target_index: int - ) -> List[Tuple[int, Point3]]: - blend_shape = self.geometry_meshes[mesh_index].blend_shapes[ - blend_shape_target_index - ] - indices = list(blend_shape.deltas.keys()) - - deltas: List[Point3] = [] - for i in indices: - deltas.append(blend_shape.deltas[i]) - - if not deltas: - return [] - - return list(zip(indices, deltas)) - - def get_all_skin_weights_values_for_mesh( - self, mesh_index: int - ) -> List[List[float]]: - skin_weight_values = [] - mesh = self.geometry_meshes[mesh_index] - for i in range(len(mesh.topology.positions)): - skin_weight_values.append(mesh.skin_weights.values[i]) - - return skin_weight_values - - def get_skin_weight_matrix_for_mesh( - self, mesh_index: int - ) -> List[List[Tuple[int, float]]]: - vertex_position_count = len(self.geometry_meshes[mesh_index].topology.positions) - - joint_indices = self.get_all_skin_weights_joint_indices_for_mesh(mesh_index) - if len(joint_indices) != vertex_position_count: - raise DNAViewerError( - "Number of joint indices and vertex count don't match!" - ) - - skin_weight_values = self.get_all_skin_weights_values_for_mesh(mesh_index) - - if len(skin_weight_values) != vertex_position_count: - raise DNAViewerError( - "Number of skin weight values and vertex count don't match!" - ) - if len(joint_indices) != len(skin_weight_values): - raise DNAViewerError( - "Number of skin weight values and joint indices count don't match for vertex!" - ) - - weight_matrix = [] - for indices, values in zip(joint_indices, skin_weight_values): - if not indices: - raise DNAViewerError( - "JointIndexArray for vertex can't be less than one!" - ) - vertex_weights = [] - for joint_index, skin_weight_value in zip(indices, values): - vertex_weights.append((joint_index, skin_weight_value)) - weight_matrix.append(vertex_weights) - return weight_matrix - - def get_vertex_texture_coordinates_for_mesh(self, mesh_index: int) -> List[UV]: - return self.geometry_meshes[mesh_index].topology.texture_coordinates - - def get_vertex_positions_for_mesh_index(self, mesh_index: int) -> List[Point3]: - return self.geometry_meshes[mesh_index].topology.positions - - def get_vertex_layout_positions_for_mesh_index(self, mesh_index: int) -> List[int]: - return [ - item.position_index - for item in self.geometry_meshes[mesh_index].topology.layouts - ] - - def get_faces(self, mesh_index: int) -> List[List[int]]: - return self.geometry_meshes[mesh_index].topology.face_vertex_layouts - - def get_polygon_faces_and_connects( - self, - mesh_index: int = None, - dna_faces: List[List[int]] = None, - dna_vertex_layout_positions: List[int] = None, - ) -> Tuple[List[int], List[int]]: - if mesh_index is None: - if None in (dna_faces, dna_vertex_layout_positions): - raise DNAViewerError( - "get_polygon_faces_and_connects -> Must provide either mesh_index or dna_faces and dna_vertex_layout_positions" - ) - if dna_faces is None: - dna_faces = self.get_faces(mesh_index) - if dna_vertex_layout_positions is None: - dna_vertex_layout_positions = ( - self.get_vertex_layout_positions_for_mesh_index(mesh_index) - ) - - polygon_faces = [] - polygon_connects = [] - for vertices_layout_index_array in dna_faces: - polygon_faces.append(len(vertices_layout_index_array)) - for vertex_layout_index_array in vertices_layout_index_array: - polygon_connects.append( - dna_vertex_layout_positions[vertex_layout_index_array] - ) - - return polygon_faces, polygon_connects - - def get_layouts_for_mesh_index(self, mesh_index: int) -> List[Layout]: - return self.geometry_meshes[mesh_index].topology.layouts - - def get_texture_coordinate_index(self, mesh_index: int, layout_id: int) -> int: - return ( - self.geometry_meshes[mesh_index] - .topology.layouts[layout_id] - .texture_coordinate_index - ) - - def has_blend_shapes(self, mesh_index: int) -> bool: - return ( - len([bs.channel for bs in self.geometry_meshes[mesh_index].blend_shapes]) - > 0 - ) - - def get_lowest_lod_containing_meshes( - self, mesh_indices: List[int] - ) -> Optional[int]: - unique_mesh_indices = set(mesh_indices) - for lod in range(self.get_lod_count()): - if any(list(unique_mesh_indices & set(self.get_mesh_indices_for_lod(lod)))): - return lod - return None - - def get_meshes_by_lods(self, mesh_indices: List[int]) -> List[List[int]]: - result_list = [] - for lod in range(self.get_lod_count()): - temp = list(set(mesh_indices) & set(self.get_mesh_indices_for_lod(lod))) - result_list.append(temp) - return result_list - - def get_all_meshes_grouped_by_lod(self) -> List[List[int]]: - """ - Gets the list of list of mesh indices grouped by the lod number. - - @type dna: DNA - @param dna: Instance of DNA. - - @rtype: List[List[int]] - @returns: The list of list of mesh indices grouped by the lod number - """ - - result: List[List[int]] = [] - - for lod in range(self.get_lod_count()): - mesh_indices = [] - for mesh_index in self.get_mesh_indices_for_lod(lod): - mesh_indices.append(mesh_index) - result.append(mesh_indices) - - return result - - def get_blend_shapes(self, mesh_index: int) -> List[BlendShape]: - return self.geometry_meshes[mesh_index].blend_shapes - - def get_mesh_id_from_mesh_name(self, mesh_name: str) -> Optional[int]: - return self.meshes_mapping.get(mesh_name, None) diff --git a/scripts/dnalib/geometry.py b/scripts/dnalib/geometry.py deleted file mode 100644 index 803d1ed..0000000 --- a/scripts/dnalib/geometry.py +++ /dev/null @@ -1,283 +0,0 @@ -from typing import Dict, List, Optional, Tuple, cast - -from dna import BinaryStreamReader as DNAReader - -from ..model import UV, BlendShape, Layout, Mesh, Point3, SkinWeightsData, Topology -from .definition import Definition -from .layer import Layer - - -class Geometry(Definition): - def __init__(self, reader: DNAReader, layers: Optional[List[Layer]]) -> None: - super().__init__(reader, layers) - self.geometry_meshes: List[Mesh] = [] - self.geometry_read = False - - def start_read(self) -> None: - super().start_read() - self.geometry_read = False - - def is_read(self) -> bool: - return super().is_read() and self.geometry_read - - def read(self) -> None: - """ - Starts reading in the mesh from the geometry part of the DNA - """ - super().read() - - if not self.geometry_read and self.layer_enabled(Layer.geometry): - self.geometry_read = True - self.geometry_meshes = [] - for lod in range(self.get_lod_count()): - for mesh_index in self.get_mesh_indices_for_lod(lod): - self.geometry_meshes.append(self.add_mesh(mesh_index)) - - def get_maximum_influence_per_vertex(self, mesh_index: int) -> int: - return cast(int, self.reader.getMaximumInfluencePerVertex(meshIndex=mesh_index)) - - def get_vertex_position_count(self, mesh_index: int) -> int: - return cast(int, self.reader.getVertexPositionCount(mesh_index)) - - def get_skin_weights_values( - self, mesh_index: int, vertex_index: int - ) -> List[float]: - return cast( - List[float], - self.reader.getSkinWeightsValues( - meshIndex=mesh_index, vertexIndex=vertex_index - ), - ) - - def get_skin_weights_joint_indices( - self, mesh_index: int, vertex_index: int - ) -> List[int]: - return cast( - List[int], - self.reader.getSkinWeightsJointIndices( - meshIndex=mesh_index, vertexIndex=vertex_index - ), - ) - - def get_vertex_texture_coordinate_count(self, mesh_index: int) -> int: - return cast( - int, self.reader.getVertexTextureCoordinateCount(meshIndex=mesh_index) - ) - - def get_vertex_texture_coordinate( - self, mesh_index: int, texture_coordinate_index: int - ) -> Tuple[float, float]: - return cast( - Tuple[float, float], - self.reader.getVertexTextureCoordinate( - meshIndex=mesh_index, textureCoordinateIndex=texture_coordinate_index - ), - ) - - def get_face_count(self, mesh_index: int) -> int: - return cast(int, self.reader.getFaceCount(meshIndex=mesh_index)) - - def get_face_vertex_layout_indices( - self, mesh_index: int, face_index: int - ) -> List[int]: - return cast( - List[int], - self.reader.getFaceVertexLayoutIndices( - meshIndex=mesh_index, faceIndex=face_index - ), - ) - - def get_vertex_layout( - self, mesh_index: int, layout_index: int - ) -> Tuple[int, int, int]: - return cast( - Tuple[int, int, int], - self.reader.getVertexLayout(meshIndex=mesh_index, layoutIndex=layout_index), - ) - - def get_vertex_layout_count(self, mesh_index: int) -> int: - return cast(int, self.reader.getVertexLayoutCount(meshIndex=mesh_index)) - - def get_vertex_position( - self, mesh_index: int, vertex_index: int - ) -> Tuple[float, float, float]: - return cast( - Tuple[float, float, float], - self.reader.getVertexPosition( - meshIndex=mesh_index, vertexIndex=vertex_index - ), - ) - - def get_blend_shape_target_vertex_indices( - self, mesh_index: int, blend_shape_target_index: int - ) -> List[int]: - return cast( - List[int], - self.reader.getBlendShapeTargetVertexIndices( - meshIndex=mesh_index, blendShapeTargetIndex=blend_shape_target_index - ), - ) - - def get_blend_shape_target_delta_count( - self, mesh_index: int, blend_shape_target_index: int - ) -> int: - return cast( - int, - self.reader.getBlendShapeTargetDeltaCount( - meshIndex=mesh_index, blendShapeTargetIndex=blend_shape_target_index - ), - ) - - def get_blend_shape_target_delta( - self, mesh_index: int, blend_shape_target_index: int, delta_index: int - ) -> Tuple[int, int, int]: - return cast( - Tuple[int, int, int], - self.reader.getBlendShapeTargetDelta( - meshIndex=mesh_index, - blendShapeTargetIndex=blend_shape_target_index, - deltaIndex=delta_index, - ), - ) - - def get_blend_shape_target_count(self, mesh_index: int) -> int: - return cast(int, self.reader.getBlendShapeTargetCount(meshIndex=mesh_index)) - - def get_blend_shape_channel_index( - self, mesh_index: int, blend_shape_target_index: int - ) -> int: - return cast( - int, - self.reader.getBlendShapeChannelIndex( - meshIndex=mesh_index, blendShapeTargetIndex=blend_shape_target_index - ), - ) - - def add_mesh(self, mesh_index: int) -> Mesh: - mesh = Mesh() - mesh.name = self.get_mesh_name(mesh_index) - mesh.topology = self.add_mesh_topology(mesh_index) - mesh.skin_weights = self.add_mesh_skin_weights(mesh_index) - mesh.blend_shapes = self.add_mesh_blend_shapes(mesh_index) - return mesh - - def add_mesh_skin_weights(self, mesh_index: int) -> SkinWeightsData: - """Reads in the skin weights""" - skin_weights = SkinWeightsData() - for vertex_index in range(self.get_vertex_position_count(mesh_index)): - skin_weights.values.append( - self.get_skin_weights_values(mesh_index, vertex_index) - ) - skin_weights.joint_indices.append( - self.get_skin_weights_joint_indices(mesh_index, vertex_index) - ) - - return skin_weights - - def add_mesh_topology(self, mesh_index: int) -> Topology: - """Reads in the positions, texture coordinates, normals, layouts and face vertex layouts""" - topology = Topology() - topology.positions = self.add_positions(mesh_index) - topology.texture_coordinates = self.add_texture_coordinates(mesh_index) - topology.layouts = self.add_layouts(mesh_index) - topology.face_vertex_layouts = self.add_face_vertex_layouts(mesh_index) - return topology - - def add_face_vertex_layouts(self, mesh_index: int) -> List[List[int]]: - """Reads in the face vertex layouts""" - face_vertex_layouts = [] - - for face_index in range(self.get_face_count(mesh_index)): - face_vertex_layouts.append( - self.get_face_vertex_layout_indices(mesh_index, face_index) - ) - - return face_vertex_layouts - - def add_layouts(self, mesh_index: int) -> List[Layout]: - """Reads in the vertex layouts""" - layouts = [] - - for layout_index in range(self.get_vertex_layout_count(mesh_index)): - ( - position_id, - texture_coordinate_id, - _, - ) = self.get_vertex_layout(mesh_index, layout_index) - layouts.append( - Layout( - position_index=position_id, - texture_coordinate_index=texture_coordinate_id, - ) - ) - return layouts - - def add_texture_coordinates(self, mesh_index: int) -> List[UV]: - """Reads in the texture coordinates""" - texture_coordinates = [] - for texture_coordinate_index in range( - self.get_vertex_texture_coordinate_count(mesh_index) - ): - u, v = self.get_vertex_texture_coordinate( - mesh_index, texture_coordinate_index - ) - texture_coordinates.append(UV(u=u, v=v)) - return texture_coordinates - - def add_positions(self, mesh_index: int) -> List[Point3]: - """Reads in the vertex positions""" - - positions = [] - for vertex_index in range(self.get_vertex_position_count(mesh_index)): - x, y, z = self.get_vertex_position(mesh_index, vertex_index) - positions.append(Point3(x=x, y=y, z=z)) - return positions - - def read_target_deltas( - self, mesh_index: int, blend_shape_target_index: int - ) -> Dict[int, Point3]: - """ - Reads in the target deltas - - @rtype: Dict[int, Point3] - @returns: Mapping of vertex indices to positions - """ - - result: Dict[int, Point3] = {} - - vertices = self.get_blend_shape_target_vertex_indices( - mesh_index, blend_shape_target_index - ) - - blend_shape_target_delta_count = self.get_blend_shape_target_delta_count( - mesh_index, blend_shape_target_index - ) - for delta_index in range(blend_shape_target_delta_count): - x, y, z = self.get_blend_shape_target_delta( - mesh_index, blend_shape_target_index, delta_index - ) - result[vertices[delta_index]] = Point3(x=x, y=y, z=z) - return result - - def add_mesh_blend_shapes(self, mesh_index: int) -> List[BlendShape]: - """ - Reads in the blend shapes - - @type mesh_index: int - @param mesh_index: The mesh index - """ - - blend_shape_target_count = self.get_blend_shape_target_count(mesh_index) - blend_shapes = [] - for blend_shape_target_index in range(blend_shape_target_count): - blend_shapes.append( - BlendShape( - channel=self.get_blend_shape_channel_index( - mesh_index, blend_shape_target_index - ), - deltas=self.read_target_deltas( - mesh_index, blend_shape_target_index - ), - ) - ) - return blend_shapes diff --git a/scripts/dnalib/layer.py b/scripts/dnalib/layer.py deleted file mode 100644 index 1e9c641..0000000 --- a/scripts/dnalib/layer.py +++ /dev/null @@ -1,9 +0,0 @@ -from enum import Enum - - -class Layer(Enum): - descriptor = 1 - definition = 2 - behavior = 3 - geometry = 4 - all = 5