From dd2318e54b65e8c84d925a5c50cc721c3ce3e7e4 Mon Sep 17 00:00:00 2001 From: Jeffreytsai1004 Date: Mon, 13 Jan 2025 23:39:54 +0800 Subject: [PATCH] Update --- scripts/MetaFusion.py | 441 +------ scripts/dna_viewer/__init__.py | 23 - scripts/dna_viewer/api.py | 42 - scripts/dna_viewer/builder/__init__.py | 0 scripts/dna_viewer/builder/builder.py | 433 ------- scripts/dna_viewer/builder/config.py | 257 ---- scripts/dna_viewer/builder/joint.py | 78 -- scripts/dna_viewer/builder/maya/__init__.py | 0 scripts/dna_viewer/builder/maya/mesh.py | 421 ------- .../dna_viewer/builder/maya/skin_weights.py | 201 ---- scripts/dna_viewer/builder/maya/util.py | 81 -- scripts/dna_viewer/builder/mesh.py | 114 -- scripts/dna_viewer/builder/rig_builder.py | 290 ----- scripts/dna_viewer/common.py | 11 - scripts/dna_viewer/dnalib/__init__.py | 0 scripts/dna_viewer/dnalib/behavior.py | 371 ------ scripts/dna_viewer/dnalib/definition.py | 333 ------ scripts/dna_viewer/dnalib/descriptor.py | 129 -- scripts/dna_viewer/dnalib/dnalib.py | 250 ---- scripts/dna_viewer/dnalib/geometry.py | 283 ----- scripts/dna_viewer/dnalib/layer.py | 9 - scripts/dna_viewer/model.py | 175 --- scripts/dna_viewer/ui/__init__.py | 0 scripts/dna_viewer/ui/app.css | 66 -- scripts/dna_viewer/ui/app.py | 1034 ----------------- scripts/dna_viewer/ui/widgets.py | 111 -- scripts/dna_viewer/version.py | 1 - 27 files changed, 1 insertion(+), 5153 deletions(-) delete mode 100644 scripts/dna_viewer/__init__.py delete mode 100644 scripts/dna_viewer/api.py delete mode 100644 scripts/dna_viewer/builder/__init__.py delete mode 100644 scripts/dna_viewer/builder/builder.py delete mode 100644 scripts/dna_viewer/builder/config.py delete mode 100644 scripts/dna_viewer/builder/joint.py delete mode 100644 scripts/dna_viewer/builder/maya/__init__.py delete mode 100644 scripts/dna_viewer/builder/maya/mesh.py delete mode 100644 scripts/dna_viewer/builder/maya/skin_weights.py delete mode 100644 scripts/dna_viewer/builder/maya/util.py delete mode 100644 scripts/dna_viewer/builder/mesh.py delete mode 100644 scripts/dna_viewer/builder/rig_builder.py delete mode 100644 scripts/dna_viewer/common.py delete mode 100644 scripts/dna_viewer/dnalib/__init__.py delete mode 100644 scripts/dna_viewer/dnalib/behavior.py delete mode 100644 scripts/dna_viewer/dnalib/definition.py delete mode 100644 scripts/dna_viewer/dnalib/descriptor.py delete mode 100644 scripts/dna_viewer/dnalib/dnalib.py delete mode 100644 scripts/dna_viewer/dnalib/geometry.py delete mode 100644 scripts/dna_viewer/dnalib/layer.py delete mode 100644 scripts/dna_viewer/model.py delete mode 100644 scripts/dna_viewer/ui/__init__.py delete mode 100644 scripts/dna_viewer/ui/app.css delete mode 100644 scripts/dna_viewer/ui/app.py delete mode 100644 scripts/dna_viewer/ui/widgets.py delete mode 100644 scripts/dna_viewer/version.py diff --git a/scripts/MetaFusion.py b/scripts/MetaFusion.py index 38b6a04..5f28270 100644 --- a/scripts/MetaFusion.py +++ b/scripts/MetaFusion.py @@ -1,440 +1 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#===================================== IMPORTS ===================================== -# Standard library imports -import os -import sys -import webbrowser -import locale - -# Qt imports -from PySide2 import QtWidgets, QtCore, QtGui -from shiboken2 import wrapInstance - -# Maya imports -from maya import OpenMayaUI as omui -import maya.cmds as cmds -import maya.mel as mel - -#===================================== IMPORT MODULES ===================================== -import BodyPrep -import BatchImport -import DNA_Viewer -<<<<<<< Updated upstream -======= - ->>>>>>> Stashed changes -#===================================== CONSTANTS ===================================== -# Tool info -TOOL_NAME = "MetaFusion" -TOOL_VERSION = "Beta v1.0.0" -TOOL_AUTHOR = "CGNICO" -TOOL_LANG = 'en_US' -# UI Constants -TOOL_WSCL_NAME = "MetaFusionWorkSpaceControl" -<<<<<<< Updated upstream -TOOL_HELP_URL = f"http://10.72.61.59:3000/ArtGroup/{TOOL_NAME}/wiki" -======= -TOOL_HELP_URL = f"https://gitea.cgnico.com/CGNICO/MetaFusion/wiki" ->>>>>>> Stashed changes -DEFAULT_WINDOW_SIZE = (500, 800) - -# Paths -TOOL_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/") -SCRIPTS_PATH = os.path.join(TOOL_PATH, "scripts").replace("\\", "/") -ICONS_PATH = os.path.join(TOOL_PATH, "icons").replace("\\", "/") -TOOL_ICON = os.path.join(ICONS_PATH, "logo.png").replace("\\", "/") - -# Metahuman paths -DATA_PATH = os.path.join(TOOL_PATH, "data").replace("\\", "/") -DNA_PATH = os.path.join(DATA_PATH, "dna").replace("\\", "/") -BODY_PATH = os.path.join(DATA_PATH, "body").replace("\\", "/") -IMG_PATH = os.path.join(DATA_PATH, "img").replace("\\", "/") -MAP_PATH = os.path.join(DATA_PATH, "map").replace("\\", "/") -MASKS_PATH = os.path.join(DATA_PATH, "masks").replace("\\", "/") -SHADERS_PATH = os.path.join(DATA_PATH, "shaders").replace("\\", "/") -MH4_PATH = os.path.join(DATA_PATH, "mh4").replace("\\", "/") -MH4_DNA_PATH = os.path.join(MH4_PATH, "dna").replace("\\", "/") -OUT_PATH = os.path.join(DATA_PATH, "out").replace("\\", "/") -SAVE_PATH = os.path.join(DATA_PATH, "save").replace("\\", "/") -MAYA_VERSION = cmds.about(version=True) -PLUGIN_PATH = os.path.join(TOOL_PATH, "plugins", f"{MAYA_VERSION}").replace("\\", "/") -if not os.path.exists(PLUGIN_PATH): - cmds.warning(f"Plugin path not found: {PLUGIN_PATH}") - -print(f"TOOL_PATH: {TOOL_PATH}") -print(f"SCRIPTS_PATH: {SCRIPTS_PATH}") -print(f"ICONS_PATH: {ICONS_PATH}") -print(f"TOOL_ICON: {TOOL_ICON}") -print(f"DATA_PATH: {DATA_PATH}") -print(f"DNA_PATH: {DNA_PATH}") -print(f"BODY_PATH: {BODY_PATH}") -print(f"IMG_PATH: {IMG_PATH}") -print(f"MAP_PATH: {MAP_PATH}") -print(f"MASKS_PATH: {MASKS_PATH}") -print(f"SHADERS_PATH: {SHADERS_PATH}") - - -#===================================== LANGUAGE SETTINGS ===================================== -TOOL_LANG = 'en_US' -SUPPORTED_LANGUAGES = ['en_US', 'zh_CN'] - -LANG = { - "en_US": { - "MetaFusion": "MetaFusion", - "Prepare": "Prepare", - "Body Prepare": "Body Prepare", - "DNA Edit": "DNA Edit", - "Open DNA Viewer": "Open DNA Viewer", - "Import": "Import", - "Batch Import": "Batch Import", - "Help": "Help", - "Switch Language": "Switch Language", - "Language switched": "Language switched", - "EN": "EN", - "ZH": "ZH", - "English": "English", - "Chinese": "Chinese" - }, - "zh_CN": { - "MetaFusion": "MetaFusion", - "Prepare": "准备", - "Body Prepare": "身体准备", - "DNA Edit": "DNA 编辑", - "Open DNA Viewer": "打开 DNA 查看器", - "Import": "导入", - "Batch Import": "批量导入", - "Help": "帮助", - "Switch Language": "切换语言", - "Language switched": "语言已切换", - "EN": "英文", - "ZH": "中文", - "English": "英语", - "Chinese": "中文" - } -} - -#===================================== UTILITY FUNCTIONS ===================================== -def get_system_encoding(): - encoding = sys.getdefaultencoding() - if encoding.lower() == 'ascii': - encoding = locale.getpreferredencoding() - return encoding - -def maya_main_window(): - main_window_ptr = omui.MQtUtil.mainWindow() - return wrapInstance(int(main_window_ptr), QtWidgets.QWidget) - -#===================================== UI COMPONENTS ===================================== -class MainButton(QtWidgets.QPushButton): - DEFAULT_COLORS = {"normal": "#D0D0D0", "hover": "#E0E0E0", "pressed": "#C0C0C0"} - - def __init__(self, text="", icon=None, color=None, hover_color=None, pressed_color=None): - super().__init__(text) - if icon: - self.setIcon(icon) - self.setIconSize(QtCore.QSize(24, 24)) - self.setMinimumHeight(30) - colors = { - "normal": color or self.DEFAULT_COLORS["normal"], - "hover": hover_color or self.DEFAULT_COLORS["hover"], - "pressed": pressed_color or self.DEFAULT_COLORS["pressed"] - } - self.setStyleSheet(self._generate_style_sheet(**colors)) - - @staticmethod - def _generate_style_sheet(normal, hover, pressed): - return f""" - QPushButton {{ - background-color: {normal}; - color: #303030; - border-radius: 10px; - padding: 5px; - font-weight: bold; - text-align: center; - }} - QPushButton:hover {{ - background-color: {hover}; - }} - QPushButton:pressed {{ - background-color: {pressed}; - }} - """ - -class BottomButton(QtWidgets.QPushButton): - def __init__(self, text="", icon=None): - super().__init__(text) - self.setMinimumHeight(20) - self.setStyleSheet(self._generate_style_sheet()) - self.setFont(QtGui.QFont("Microsoft YaHei", 10)) - - @staticmethod - def _generate_style_sheet(): - return """ - QPushButton { - background-color: transparent; - border: none; - color: gray; - font-weight: bold; - } - QPushButton:hover { - color: black; - } - """ - -#===================================== MAIN WINDOW ===================================== -class MainWindow(QtWidgets.QWidget): - - instance = None - - def __init__(self, parent=maya_main_window()): - self.load_required_plugins() - - super(MainWindow, self).__init__(parent) - self.setWindowTitle(f"{TOOL_NAME} - {TOOL_VERSION}") - self.setObjectName(TOOL_PATH) - self.setWindowFlags(QtCore.Qt.Window) - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred) - self.setMinimumSize(300, 800) - - self.create_widgets() - self.create_layouts() - self.create_connections() - - if os.path.exists(TOOL_ICON): - self.setWindowIcon(QtGui.QIcon(TOOL_ICON)) - else: - print(f"WARNING: Icon file not found: {TOOL_ICON}") - - def load_required_plugins(self): - try: - if PLUGIN_PATH not in os.environ.get('MAYA_PLUG_IN_PATH', ''): - if 'MAYA_PLUG_IN_PATH' in os.environ: - os.environ['MAYA_PLUG_IN_PATH'] = f"{PLUGIN_PATH};{os.environ['MAYA_PLUG_IN_PATH']}" - else: - os.environ['MAYA_PLUG_IN_PATH'] = PLUGIN_PATH - - required_plugins = ['embeddedRL4.mll'] - for plugin in required_plugins: - plugin_path = os.path.join(PLUGIN_PATH, plugin) - if os.path.exists(plugin_path): - try: - if not cmds.pluginInfo(plugin, query=True, loaded=True): - cmds.loadPlugin(plugin_path) - print(f"Successfully loaded plugin: {plugin}") - except Exception as e: - cmds.warning(f"Failed to load plugin {plugin}: {str(e)}") - else: - cmds.warning(f"Plugin not found: {plugin_path}") - except Exception as e: - cmds.warning(f"Error loading plugins: {str(e)}") - - @classmethod - def show_window(cls): - try: - if cmds.workspaceControl(TOOL_WSCL_NAME, exists=True): - cmds.deleteUI(TOOL_WSCL_NAME, control=True) - - if cls.instance is not None: - try: - cls.instance.close() - cls.instance.deleteLater() - except Exception: - pass - cls.instance = cls() - cls.instance.dock_to_maya() - return cls.instance - except Exception as e: - print(f"Error showing {TOOL_NAME} window: {e}") - return None - - def dock_to_maya(self): - if cmds.workspaceControl(TOOL_WSCL_NAME, exists=True): - cmds.deleteUI(TOOL_WSCL_NAME) - try: - workspace_control = cmds.workspaceControl( - TOOL_WSCL_NAME, - label=TOOL_NAME, - floating=True, - retain=True, - resizeWidth=True, - initialWidth=500, - minimumWidth=500 - ) - cmds.workspaceControl(TOOL_WSCL_NAME, e=True, resizeWidth=True) - cmds.control(self.objectName(), e=True, p=workspace_control) - cmds.evalDeferred(lambda: cmds.workspaceControl(TOOL_WSCL_NAME, e=True, resizeWidth=True)) - except Exception as e: - print(f"Error creating workspace control: {e}") - -#===================================== UI COMPONENTS ===================================== - def create_widgets(self): -<<<<<<< Updated upstream - # DNA Edit group - self.dna_edit_btn = MainButton(LANG[TOOL_LANG]["DNA Edit"]) - self.dna_viewer_btn = MainButton(LANG[TOOL_LANG]["Open DNA Viewer"], color="#B8E6B3", hover_color="#C4F2BF", pressed_color="#A3D99E") - -======= - ->>>>>>> Stashed changes - # Prepare group - self.prepare_btn = MainButton(LANG[TOOL_LANG]["Prepare"]) - self.body_prepare_btn = MainButton(LANG[TOOL_LANG]["Body Prepare"], color="#FFEBA1", hover_color="#FFF5B3", pressed_color="#FFE68A") - -<<<<<<< Updated upstream -======= - # DNA Edit group - self.dna_edit_btn = MainButton(LANG[TOOL_LANG]["DNA Edit"]) - self.dna_viewer_btn = MainButton(LANG[TOOL_LANG]["Open DNA Viewer"], color="#B8E6B3", hover_color="#C4F2BF", pressed_color="#A3D99E") - - # Import group - self.import_btn = MainButton(LANG[TOOL_LANG]["Import"]) - self.batch_import_btn = MainButton(LANG[TOOL_LANG]["Batch Import"], color="#A7C6ED", hover_color="#B2D3F0", pressed_color="#8BB8E0") - ->>>>>>> Stashed changes - # Bottom buttons (existing code) - self.help_btn = BottomButton(LANG[TOOL_LANG]["Help"]) - self.help_btn.setToolTip(LANG[TOOL_LANG]["Help"]) - self.help_btn.setFixedSize(100, 20) - - self.lang_btn = BottomButton(LANG[TOOL_LANG]["ZH" if TOOL_LANG == 'en_US' else "EN"]) - self.lang_btn.setToolTip(LANG[TOOL_LANG]["Switch Language"]) - self.lang_btn.setFixedSize(30, 20) - - for button in [self.help_btn, self.lang_btn]: - button.setFont(QtGui.QFont("Microsoft YaHei", 10)) - - def create_layouts(self): - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(2, 2, 2, 2) - content_layout = QtWidgets.QVBoxLayout() - content_layout.setContentsMargins(5, 5, 5, 5) - bottom_layout = QtWidgets.QHBoxLayout() - bottom_layout.setContentsMargins(5, 0, 5, 5) - - prepare_group = QtWidgets.QGroupBox("Prepare") - prepare_layout = QtWidgets.QVBoxLayout(prepare_group) - prepare_layout.addWidget(self.body_prepare_btn) - content_layout.addWidget(prepare_group) - - import_group = QtWidgets.QGroupBox("Import") - import_layout = QtWidgets.QVBoxLayout(import_group) - import_layout.addWidget(self.batch_import_btn) - content_layout.addWidget(import_group) - -<<<<<<< Updated upstream - main_layout.addLayout(content_layout) - main_layout.addStretch() - - # Bottom layout (existing code) - bottom_layout = QtWidgets.QHBoxLayout() - bottom_layout.setContentsMargins(5, 0, 5, 5) - -======= - dna_edit_group = QtWidgets.QGroupBox("DNA Edit") - dna_edit_layout = QtWidgets.QVBoxLayout(dna_edit_group) - dna_edit_layout.addWidget(self.dna_viewer_btn) - content_layout.addWidget(dna_edit_group) - ->>>>>>> Stashed changes - icon_label = QtWidgets.QLabel() - if os.path.exists(TOOL_ICON): - icon = QtGui.QPixmap(TOOL_ICON).scaled(24, 24, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) - icon_label.setPixmap(icon) - - version_label = QtWidgets.QLabel(f"{TOOL_VERSION}") - version_label.setStyleSheet("color: gray; font-size: 12px;") - - bottom_layout.addWidget(icon_label) - bottom_layout.addWidget(version_label) - bottom_layout.addWidget(self.help_btn) - bottom_layout.addWidget(self.lang_btn) - - main_layout.addLayout(content_layout) - main_layout.addLayout(bottom_layout) - main_layout.addStretch() - - def create_connections(self): - self.body_prepare_btn.clicked.connect(self.run_body_prepare) - self.dna_viewer_btn.clicked.connect(self.run_dna_viewer) -<<<<<<< Updated upstream - - # Existing connections -======= - self.batch_import_btn.clicked.connect(self.run_batch_import) ->>>>>>> Stashed changes - self.help_btn.clicked.connect(self.help) - self.lang_btn.clicked.connect(self.switch_language) - -#===================================== FUNCTIONS ===================================== - #===================================== MAIN FUNCTIONS ===================================== - def run_body_prepare(self): - BodyPrep.run() - - def run_batch_import(self): - BatchImport.run() - - def run_dna_viewer(self): -<<<<<<< Updated upstream - import DNA_Viewer - DNA_Viewer.show() - -======= - DNA_Viewer.show() ->>>>>>> Stashed changes - - #===================================== BOTTOM LAYOUT ===================================== - def help(self): - webbrowser.open(TOOL_HELP_URL) - - def switch_language(self): - global TOOL_LANG - TOOL_LANG = 'en_US' if TOOL_LANG == 'zh_CN' else 'zh_CN' - self.lang_btn.setText("ZH" if TOOL_LANG == 'en_US' else "EN") - self.retranslate_ui() - QtWidgets.QToolTip.showText( - self.lang_btn.mapToGlobal(QtCore.QPoint(0, -30)), - "Language switched" if TOOL_LANG == 'en_US' else "语言已切换", - self.lang_btn - ) - - def retranslate_ui(self): -<<<<<<< Updated upstream - - # Update function button translations -======= ->>>>>>> Stashed changes - self.load_dna_btn.setText(LANG[TOOL_LANG]["Load DNA"]) - self.body_prepare_btn.setText(LANG[TOOL_LANG]["Body Prepare"]) - self.dna_viewer_btn.setText(LANG[TOOL_LANG]["Open DNA Viewer"]) - self.batch_import_btn.setText(LANG[TOOL_LANG]["Batch Import"]) - - self.help_btn.setText(LANG[TOOL_LANG]["Help"]) - self.lang_btn.setText("ZH" if TOOL_LANG == 'en_US' else "EN") - - for button in [self.body_prepare_btn, self.dna_viewer_btn, self.batch_import_btn]: - button.setFont(QtGui.QFont("Microsoft YaHei", 10)) - - for button in [self.help_btn, self.lang_btn]: - button.setFont(QtGui.QFont("Microsoft YaHei", 10)) - - self.dna_file_label.setText(LANG[TOOL_LANG]["DNA File:"]) - - def on_dna_selected(self, dna_path): - """当DNA被选中时""" - global DNA_File - DNA_File = dna_path - self.dna_file_input.setText(DNA_File) - print(f"Selected DNA file: {DNA_File}") - - def on_dna_file_changed(self): - """当DNA文件输入框内容改变时""" - global DNA_File - DNA_File = self.dna_file_input.text() - print(f"DNA file path updated: {DNA_File}") - -#===================================== LAUNCH FUNCTIONS ===================================== -def show(): - return MainWindow.show_window() \ No newline at end of file + \ No newline at end of file diff --git a/scripts/dna_viewer/__init__.py b/scripts/dna_viewer/__init__.py deleted file mode 100644 index 055a12a..0000000 --- a/scripts/dna_viewer/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -from .api import build_meshes, build_rig -from .builder.config import Config, RigConfig -from .builder.maya.skin_weights import ( - get_skin_weights_from_scene, - set_skin_weights_to_scene, -) -from .dnalib.dnalib import DNA -from .dnalib.layer import Layer -from .ui.app import show -from .version import __version__ - -__all__ = [ - "DNA", - "build_rig", - "build_meshes", - "show", - "get_skin_weights_from_scene", - "set_skin_weights_to_scene", - "Config", - "RigConfig", - "Layer", - "__version__", -] diff --git a/scripts/dna_viewer/api.py b/scripts/dna_viewer/api.py deleted file mode 100644 index 73dc406..0000000 --- a/scripts/dna_viewer/api.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Optional - -from .builder.builder import Builder, BuildResult -from .builder.config import Config, RigConfig -from .builder.rig_builder import RigBuilder -from .dnalib.dnalib import DNA - - -def build_rig(dna: DNA, config: RigConfig) -> BuildResult: - """ - Used for assembling the rig with provided configuration. - - @type config: DNA - @param config: Instance of DNA - - @type config: Config - @param config: Instance of configuration - - @rtype: BuildResult - @returns: The object representing result of build - """ - - return RigBuilder(dna, config).build() - - -def build_meshes(dna: DNA, config: Optional[Config] = None) -> BuildResult: - """ - Starts the mesh building process with the provided configuration. - - @type config: DNA - @param config: Instance of DNA - - @type config: Config - @param config: Instance of configuration - - @rtype: BuildResult - @returns: The object representing result of build - """ - if config is None: - config = Config() - - return Builder(dna, config).build() diff --git a/scripts/dna_viewer/builder/__init__.py b/scripts/dna_viewer/builder/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/dna_viewer/builder/builder.py b/scripts/dna_viewer/builder/builder.py deleted file mode 100644 index 8fd5be6..0000000 --- a/scripts/dna_viewer/builder/builder.py +++ /dev/null @@ -1,433 +0,0 @@ -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/dna_viewer/builder/config.py b/scripts/dna_viewer/builder/config.py deleted file mode 100644 index f142337..0000000 --- a/scripts/dna_viewer/builder/config.py +++ /dev/null @@ -1,257 +0,0 @@ -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/dna_viewer/builder/joint.py b/scripts/dna_viewer/builder/joint.py deleted file mode 100644 index d0e17ad..0000000 --- a/scripts/dna_viewer/builder/joint.py +++ /dev/null @@ -1,78 +0,0 @@ -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/dna_viewer/builder/maya/__init__.py b/scripts/dna_viewer/builder/maya/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/dna_viewer/builder/maya/mesh.py b/scripts/dna_viewer/builder/maya/mesh.py deleted file mode 100644 index ebda703..0000000 --- a/scripts/dna_viewer/builder/maya/mesh.py +++ /dev/null @@ -1,421 +0,0 @@ -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/dna_viewer/builder/maya/skin_weights.py b/scripts/dna_viewer/builder/maya/skin_weights.py deleted file mode 100644 index 64f35f4..0000000 --- a/scripts/dna_viewer/builder/maya/skin_weights.py +++ /dev/null @@ -1,201 +0,0 @@ -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/dna_viewer/builder/maya/util.py b/scripts/dna_viewer/builder/maya/util.py deleted file mode 100644 index 69ae04f..0000000 --- a/scripts/dna_viewer/builder/maya/util.py +++ /dev/null @@ -1,81 +0,0 @@ -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/dna_viewer/builder/mesh.py b/scripts/dna_viewer/builder/mesh.py deleted file mode 100644 index 5e9c25f..0000000 --- a/scripts/dna_viewer/builder/mesh.py +++ /dev/null @@ -1,114 +0,0 @@ -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/dna_viewer/builder/rig_builder.py b/scripts/dna_viewer/builder/rig_builder.py deleted file mode 100644 index 45c5692..0000000 --- a/scripts/dna_viewer/builder/rig_builder.py +++ /dev/null @@ -1,290 +0,0 @@ -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/dna_viewer/common.py b/scripts/dna_viewer/common.py deleted file mode 100644 index ffd3c34..0000000 --- a/scripts/dna_viewer/common.py +++ /dev/null @@ -1,11 +0,0 @@ -ANALOG_GUI_HOLDER = "analog_gui" - -GUI_HOLDER = "gui" - -RIG_LOGIC_PREFIX = "rl4Embedded_" - -SKIN_WEIGHT_PRINT_RANGE = 2000 - - -class DNAViewerError(Exception): - pass diff --git a/scripts/dna_viewer/dnalib/__init__.py b/scripts/dna_viewer/dnalib/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/dna_viewer/dnalib/behavior.py b/scripts/dna_viewer/dnalib/behavior.py deleted file mode 100644 index d824890..0000000 --- a/scripts/dna_viewer/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/dna_viewer/dnalib/definition.py b/scripts/dna_viewer/dnalib/definition.py deleted file mode 100644 index b64aa99..0000000 --- a/scripts/dna_viewer/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/dna_viewer/dnalib/descriptor.py b/scripts/dna_viewer/dnalib/descriptor.py deleted file mode 100644 index c359702..0000000 --- a/scripts/dna_viewer/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/dna_viewer/dnalib/dnalib.py b/scripts/dna_viewer/dnalib/dnalib.py deleted file mode 100644 index 27a1d0f..0000000 --- a/scripts/dna_viewer/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/dna_viewer/dnalib/geometry.py b/scripts/dna_viewer/dnalib/geometry.py deleted file mode 100644 index 803d1ed..0000000 --- a/scripts/dna_viewer/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/dna_viewer/dnalib/layer.py b/scripts/dna_viewer/dnalib/layer.py deleted file mode 100644 index 1e9c641..0000000 --- a/scripts/dna_viewer/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 diff --git a/scripts/dna_viewer/model.py b/scripts/dna_viewer/model.py deleted file mode 100644 index a80563b..0000000 --- a/scripts/dna_viewer/model.py +++ /dev/null @@ -1,175 +0,0 @@ -from dataclasses import dataclass, field -from typing import Dict, List - - -@dataclass -class Point3: - """ - A model class for representing a 3 dimensional point - - Attributes - ---------- - @type x: float - @param x: The value of x - - @type y: float - @param y: The value of y - - @type z: float - @param z: The value of z - """ - - x: float = field(default=0.0) - y: float = field(default=0.0) - z: float = field(default=0.0) - - -@dataclass -class UV: - """ - A model class for holding data about the UV - - Attributes - ---------- - @type u: float - @param u: The value of u - - @type v: float - @param v: The value of v - """ - - u: float = field(default=0.0) - v: float = field(default=0.0) - - -@dataclass -class Layout: - """ - A model class for holding data about a single layout - - Attributes - ---------- - @type position_index: int - @param position_index: An index representing position - - @type texture_coordinate_index: int - @param texture_coordinate_index: A value representing the texture coordinate index - """ - - position_index: int = field(default=0) - texture_coordinate_index: int = field(default=0) - - -@dataclass -class Topology: - """ - A model class for holding data about the topology - - Attributes - ---------- - @type positions: List[Point3] - @param positions: List of points in space representing the positions - - @type texture_coordinates: List[UV] - @param texture_coordinates: List of UVs representing the positions - - @type layouts: List[Layout] - @param layouts: The list of Layout mappings - - @type face_vertex_layouts: List[List[int]] - @param face_vertex_layouts: List of face vertex layout indices by face index - """ - - positions: List[Point3] = field(default_factory=list) - texture_coordinates: List[UV] = field(default_factory=list) - layouts: List[Layout] = field(default_factory=list) - face_vertex_layouts: List[List[int]] = field(default_factory=list) - - -@dataclass -class BlendShape: - """ - A model class for holding data about the blend shape - - Attributes - ---------- - @type channel: int - @param channel: The index pointing to the blend shape name - - @type deltas: Dict[int, Point3] - @param deltas: A mapping of blend shape indices to the coordinate differences that are made by the blend shape - """ - - channel: int = field(default=None) - deltas: Dict[int, Point3] = field(default_factory=dict) - - -@dataclass -class SkinWeightsData: - """ - A model class for holding data about the skin weights - - Attributes - ---------- - - @type values: List[List[float]] - @param values: The skin weight values per vertex index - - @type joint_indices: List[List[int]] - @param joint_indices: The joint indces per vertex index - """ - - values: List[List[float]] = field(default_factory=list) - joint_indices: List[List[int]] = field(default_factory=list) - - -@dataclass -class Mesh: - """ - A model class for holding data about the mesh - - Attributes - ---------- - @type name: str - @param name: The name of the mesh - - @type topology: Topology - @param topology: Data containing the topology of the mesh - - @type skin_weights: SkinWeightsData - @param skin_weights: Data representing skin weights - - @type blend_shapes: List[BlendShape] - @param blend_shapes: The list of blend shapes for the mesh - """ - - name: str = field(default=None) - topology: Topology = field(default_factory=Topology) - skin_weights: SkinWeightsData = field(default_factory=SkinWeightsData) - blend_shapes: List[BlendShape] = field(default_factory=list) - - -@dataclass -class Joint: - """ - A model class for holding data about a single joint - - Attributes - ---------- - @type name: str - @param name: The name of the joint - - @type translation: Point3 - @param translation: A point in 3 dimensional space which represents the translation of the joint - - @type orientation: Point3 - @param orientation: A point in 3 dimensional space which represents the orientation of the joint - - @type parent_name: str - @param parent_name: The name of the parent joint - """ - - name: str = field(default=None) - translation: Point3 = field(default_factory=Point3) - orientation: Point3 = field(default_factory=Point3) - parent_name: str = field(default=None) diff --git a/scripts/dna_viewer/ui/__init__.py b/scripts/dna_viewer/ui/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/dna_viewer/ui/app.css b/scripts/dna_viewer/ui/app.css deleted file mode 100644 index 62b3bfc..0000000 --- a/scripts/dna_viewer/ui/app.css +++ /dev/null @@ -1,66 +0,0 @@ -/* Global CSS */ - -* { - font-size: 9pt; - font-family: "Open Sans"; -} - -/* General CSS */ - -QWidget { - color: #DDD; -} - -QWidget:disabled { - color: #888; -} - -QMainWindow { - background-color: #444; -} - -QDialog { - background-color: #444; -} - -QToolTip { - color: #DDD; - border: #DDD solid 1px; - background-color: #111; -} - -QToolButton { - width: 27px; - height: 27px; - background-color: #555; - border: 1px solid #333; - border-radius: 2px; - font-size: 12pt; - font-family: "Material Icons"; -} - -QPushButton { - height: 27px; - min-width: 60px; - background-color: #555; - border: 1px solid #333; - border-radius: 2px; -} - -QToolButton:disabled, -QPushButton:disabled { - color: #888; -} - -QToolButton:hover:!pressed, -QPushButton:hover:!pressed { - background-color: #666; -} - -QTextEdit, -QLineEdit { - background-color: #333; - border: 1px solid #222; -} - - diff --git a/scripts/dna_viewer/ui/app.py b/scripts/dna_viewer/ui/app.py deleted file mode 100644 index 28eb73b..0000000 --- a/scripts/dna_viewer/ui/app.py +++ /dev/null @@ -1,1034 +0,0 @@ -<<<<<<< Updated upstream -======= -#!/usr/bin/env python -# -*- coding: utf-8 -*- - ->>>>>>> Stashed changes -import logging -import os -import webbrowser -from typing import Callable, List - -from maya import cmds -from maya.cmds import confirmDialog -from PySide2.QtCore import Qt, QSize, QRect, QPoint, QCoreApplication -from PySide2.QtWidgets import ( - QApplication, - QCheckBox, - QGridLayout, - QHBoxLayout, - QLabel, - QLayout, - QMainWindow, - QMessageBox, - QProgressBar, - QPushButton, - QTabWidget, - QTreeWidget, - QTreeWidgetItem, - QTreeWidgetItemIterator, - QVBoxLayout, - QWidget, - QScrollArea -) -from PySide2.QtGui import QIcon, QPixmap -from PySide2 import QtCore, QtGui - -from .. import DNA, build_rig -from ..builder.config import RigConfig -from ..dnalib.layer import Layer -from ..version import __version__ -from .widgets import FileChooser, QHLine - - -def show() -> None: - DnaViewerWindow.show_window() - - -<<<<<<< Updated upstream -TOOL_NAME = "Delos" -WINDOW_TITLE = "DNA Viewer" -WINDOW_OBJECT = "dnaviewer" -TOOL_HELP_URL = f"http://10.72.61.59:3000/ArtGroup/{TOOL_NAME}/wiki" -======= -TOOL_NAME = "DNA Viewer" -WINDOW_TITLE = "DNA Viewer" -WINDOW_OBJECT = "dnaviewer" -TOOL_HELP_URL = f"https://gitea.cgnico.com/CGNICO/MetaFusion/wiki" ->>>>>>> Stashed changes -SPACING = 6 -WINDOW_SIZE_WIDTH_MIN = 800 -WINDOW_SIZE_WIDTH_MAX = 1200 -WINDOW_SIZE_HEIGHT_MIN = 1000 -WINDOW_SIZE_HEIGHT_MAX = 1000 -MARGIN_LEFT = 8 -MARGIN_TOP = 8 -MARGIN_RIGHT = 8 -MARGIN_BOTTOM = 8 -MARGIN_HEADER_LEFT = 0 -MARGIN_HEADER_TOP = 0 -MARGIN_HEADER_RIGHT = 0 -MARGIN_HEADER_BOTTOM = 0 -MARGIN_BODY_LEFT = 0 -MARGIN_BODY_TOP = 0 -MARGIN_BODY_RIGHT = 0 - -TOOL_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))).replace("\\", "/") -SCRIPTS_PATH = os.path.join(TOOL_PATH, "scripts").replace("\\", "/") -DATA_PATH = os.path.join(TOOL_PATH, "data").replace("\\", "/") -DNA_PATH = os.path.join(DATA_PATH, "dna").replace("\\", "/") -IMG_PATH = os.path.join(DATA_PATH, "img").replace("\\", "/") -SOURCE_PATH = os.path.join(DATA_PATH, "source").replace("\\", "/") -GUI_PATH = os.path.join(SOURCE_PATH, "gui.ma").replace("\\", "/") -ANALOG_GUI_PATH = os.path.join(SOURCE_PATH, "analog_gui.ma").replace("\\", "/") -ADDITIONAL_ASSEMBLE_SCRIPT_PATH = os.path.join(SOURCE_PATH, "additional_assemble_script.py").replace("\\", "/") - - -class FlowLayout(QLayout): - def __init__(self, parent=None): - super().__init__(parent) - self.itemList = [] - self.spacing_x = 5 - self.spacing_y = 5 - - def addItem(self, item): - self.itemList.append(item) - - def count(self): - return len(self.itemList) - - def itemAt(self, index): - if index >= 0 and index < len(self.itemList): - return self.itemList[index] - return None - - def takeAt(self, index): - if index >= 0 and index < len(self.itemList): - return self.itemList.pop(index) - return None - - def expandingDirections(self): - return Qt.Orientations(Qt.Orientation(0)) - - def hasHeightForWidth(self): - return True - - def heightForWidth(self, width): - height = self.doLayout(QRect(0, 0, width, 0), True) - return height - - def setGeometry(self, rect): - super(FlowLayout, self).setGeometry(rect) - self.doLayout(rect, False) - - def sizeHint(self): - return self.minimumSize() - - def minimumSize(self): - size = QSize() - for item in self.itemList: - size = size.expandedTo(item.minimumSize()) - size += QSize(2 * self.margin(), 2 * self.margin()) - return size - - def doLayout(self, rect, testOnly): - x = rect.x() - y = rect.y() - lineHeight = 0 - for item in self.itemList: - wid = item.widget() - spaceX = self.spacing_x - spaceY = self.spacing_y - nextX = x + item.sizeHint().width() + spaceX - if nextX - spaceX > rect.right() and lineHeight > 0: - x = rect.x() - y = y + lineHeight + spaceY - nextX = x + item.sizeHint().width() + spaceX - lineHeight = 0 - if not testOnly: - item.setGeometry(QRect(QPoint(x, y), item.sizeHint())) - x = nextX - lineHeight = max(lineHeight, item.sizeHint().height()) - return y + lineHeight - rect.y() - - -class DNABrowser(QWidget): - dna_selected = QtCore.Signal(str) # Signal: when DNA is selected - - def __init__(self, dna_path, img_path, parent=None): - super().__init__(parent) - self.dna_path = dna_path - self.img_path = img_path - self.setup_ui() - self.scan_dna_files() - self.update_grid() - - def setup_ui(self): - self.main_layout = QVBoxLayout(self) - self.main_layout.setContentsMargins(0, 0, 0, 0) - - self.flow_widget = QWidget() - self.flow_layout = FlowLayout(self.flow_widget) - self.flow_layout.setSpacing(10) - - self.scroll_area = QScrollArea() - self.scroll_area.setWidgetResizable(True) - self.scroll_area.setWidget(self.flow_widget) - self.scroll_area.setFixedHeight(350) - self.scroll_area.setStyleSheet(""" - QScrollArea { - border: 1px solid #404040; - background-color: #303030; - } - """) - - self.main_layout.addWidget(self.scroll_area) - - def scan_dna_files(self): - """Scan DNA folder and create index""" - self.dna_files = {} - if not os.path.exists(self.dna_path): - cmds.warning(f"DNA path not found: {self.dna_path}") - return - - # Default preview image path - default_preview = os.path.join(self.img_path, "Preview.png").replace("\\", "/") - - for file in os.listdir(self.dna_path): - if file.endswith('.dna'): - name = os.path.splitext(file)[0] - dna_file = os.path.join(self.dna_path, file).replace("\\", "/") - - # Find corresponding image, if not found use default image - img_file = None - for ext in ['.jpg', '.png', '.jpeg']: - img_path = os.path.join(self.img_path, f"{name}{ext}").replace("\\", "/") - if os.path.exists(img_path): - img_file = img_path - break - -<<<<<<< Updated upstream - # If no corresponding image is found, use default preview image -======= ->>>>>>> Stashed changes - if not img_file and os.path.exists(default_preview): - img_file = default_preview - - self.dna_files[name] = { - 'dna_path': dna_file, - 'img_path': img_file - } - - def update_grid(self): - """Update DNA grid""" - for i in reversed(range(self.flow_layout.count())): - self.flow_layout.itemAt(i).widget().deleteLater() - - for name, info in self.dna_files.items(): - dna_btn = self.create_dna_button(name, info) - self.flow_layout.addWidget(dna_btn) - - def create_dna_button(self, name, info): - """Create DNA button""" - btn = QPushButton() - btn.setFixedSize(180, 120) - - layout = QVBoxLayout(btn) - layout.setContentsMargins(4, 4, 4, 4) - - # Icon label - icon_label = QLabel() - icon_label.setAlignment(Qt.AlignCenter) - pixmap = QtGui.QPixmap(info['img_path']) - scaled_pixmap = pixmap.scaled(90, 90, Qt.KeepAspectRatio, Qt.SmoothTransformation) - icon_label.setPixmap(scaled_pixmap) - - # Text label - text_label = QLabel(name) - text_label.setAlignment(Qt.AlignCenter) - text_label.setStyleSheet("color: #FFFFFF;") - - layout.addWidget(icon_label) - layout.addWidget(text_label) - - btn.setStyleSheet(""" - QPushButton { - background-color: #303030; - border: 2px solid #202020; - border-radius: 6px; - } - QPushButton:hover { - background-color: #404040; - border: 2px solid #303030; - } - """) - - btn.clicked.connect(lambda: self.dna_selected.emit(info['dna_path'])) - return btn - - -class MeshTreeList(QWidget): - def __init__(self, main_window: "DnaViewerWindow") -> None: - super().__init__() - self.main_window = main_window - - # Layout - self.main_layout = QVBoxLayout() - self.main_layout.setContentsMargins( - MARGIN_BODY_LEFT, - MARGIN_BODY_TOP, - MARGIN_BODY_RIGHT, - MARGIN_BOTTOM - ) - self.setLayout(self.main_layout) - - # Widgets - self.title_label = QLabel("Meshes:") - self.scroll_area = QScrollArea() - self.tree_container = QWidget() - self.mesh_tree = self.create_mesh_tree() - self.btn_select_all = QPushButton("Select all meshes") - self.btn_deselect_all = QPushButton("Deselect all meshes") - - # Setup scroll area - self.scroll_area.setWidgetResizable(True) - self.scroll_area.setMinimumHeight(150) - self.scroll_area.setStyleSheet(""" - QScrollArea { - border: 1px solid #404040; - background-color: #303030; - } - """) - - # Setup tree container - self.tree_layout = QVBoxLayout(self.tree_container) - self.tree_layout.setContentsMargins(0, 0, 0, 0) - self.tree_layout.addWidget(self.mesh_tree) - self.scroll_area.setWidget(self.tree_container) - - # Setup buttons - self.btn_select_all.setEnabled(False) - self.btn_deselect_all.setEnabled(False) - self.button_layout = QHBoxLayout() - self.button_layout.addWidget(self.btn_select_all) - self.button_layout.addWidget(self.btn_deselect_all) - - # Layout assembly - self.main_layout.addWidget(self.title_label) - self.main_layout.addWidget(self.scroll_area) - self.main_layout.addLayout(self.button_layout) - - # Connections - self.btn_select_all.clicked.connect(self.select_all) - self.btn_deselect_all.clicked.connect(self.deselect_all) - - # ==================================================== DNA Browser ==================================================== - def scan_dna_files(self): - self.dna_files = {} - if not os.path.exists(self.dna_path): - cmds.warning(f"DNA path not found: {self.dna_path}") - return - if not os.path.exists(self.img_path): - cmds.warning(f"Image path not found: {self.img_path}") - return - for file in os.listdir(self.dna_path): - if file.endswith('.dna'): - name = os.path.splitext(file)[0] - dna_file = os.path.join(self.dna_path, file).replace("\\", "/") - - img_file = None - for ext in ['.jpg', '.png', '.jpeg']: - img_path = os.path.join(self.img_path, f"{name}{ext}").replace("\\", "/") - if os.path.exists(img_path): - img_file = img_path - break - self.dna_files[name] = { - 'dna_path': dna_file, - 'img_path': img_file - } - print(f"DNA file: {name}") - print(f" DNA path: {dna_file}") - print(f" Image path: {img_file}") - print(f" Image exists: {bool(img_file and os.path.exists(img_file))}") - - - - def create_mesh_tree(self) -> QWidget: - mesh_tree = QTreeWidget() - mesh_tree.setHeaderHidden(True) - mesh_tree.itemChanged.connect(self.tree_item_changed) - mesh_tree.setStyleSheet("background-color: #505050") - mesh_tree.setToolTip("Select mesh or meshes to add to rig") - return mesh_tree - - def fill_mesh_list( - self, lod_count: int, names: List[str], indices_names: List[List[int]] - ) -> None: - self.mesh_tree.clear() - for i in range(lod_count): - parent = QTreeWidgetItem(self.mesh_tree) - parent.setText(0, f"LOD {i}") - parent.setFlags(parent.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable) - meshes_in_lod = indices_names[i] - for mesh_index in meshes_in_lod: - child = QTreeWidgetItem(parent) - child.setFlags(child.flags() | Qt.ItemIsUserCheckable) - child.setText(0, f"{names[mesh_index]}") - child.setCheckState(0, Qt.Unchecked) - self.mesh_tree.setItemExpanded(parent, True) - - def get_selected_meshes(self) -> List[int]: - meshes = [] - - iterator = QTreeWidgetItemIterator( - self.mesh_tree, QTreeWidgetItemIterator.Checked - ) - while iterator.value(): - item = iterator.value() - mesh_name = item.text(0) - mesh_index = self.main_window.dna.get_mesh_id_from_mesh_name(mesh_name) - if mesh_index is not None: - meshes.append(mesh_index) - - iterator += 1 - - return meshes - - def select_all(self) -> None: - self.iterate_over_items(Qt.Checked) - - def deselect_all(self) -> None: - self.iterate_over_items(Qt.Unchecked) - - def iterate_over_items(self, state: Qt.CheckState) -> None: - item = self.mesh_tree.invisibleRootItem() - for index in range(item.childCount()): - child = item.child(index) - child.setCheckState(0, state) - - def tree_item_changed(self) -> None: - """The method that gets called when a tree item gets its value changed""" - - meshes = self.get_selected_meshes() - - if meshes: - self.main_window.skin_cb.setEnabled(self.main_window.joints_cb.checkState()) - self.main_window.blend_shapes_cb.setEnabled(True) - self.main_window.process_btn.setEnabled(True) - self.main_window.rig_logic_cb.setEnabled(False) - - if len(meshes) == self.main_window.dna.get_mesh_count(): - self.main_window.rig_logic_cb.setEnabled( - self.main_window.joints_cb.checkState() - and self.main_window.blend_shapes_cb.checkState() - and self.main_window.skin_cb.checkState() - and self.main_window.select_gui_path.get_file_path() is not None - and self.main_window.select_analog_gui_path.get_file_path() - is not None - and self.main_window.select_aas_path.get_file_path() is not None - ) - else: - self.main_window.skin_cb.setEnabled(False) - self.main_window.blend_shapes_cb.setEnabled(False) - self.main_window.process_btn.setEnabled( - self.main_window.joints_cb.checkState() - ) - - -class DnaViewerWindow(QMainWindow): - _instance: "DnaViewerWindow" = None - main_widget: QWidget = None - select_dna_path: FileChooser = None - load_dna_btn: QPushButton = None - mesh_tree_list: QWidget = None - joints_cb: QCheckBox = None - blend_shapes_cb: QCheckBox = None - skin_cb: QCheckBox = None - rig_logic_cb: QCheckBox = None - ctrl_attributes_on_root_joint_cb: QCheckBox = None - animated_map_attributes_on_root_joint_cb: QCheckBox = None - mesh_name_to_blend_shape_channel_name_cb: QCheckBox = None - key_frames_cb: QCheckBox = None - select_gui_path: FileChooser = None - select_analog_gui_path: FileChooser = None - select_aas_path: FileChooser = None - process_btn: QPushButton = None - progress_bar: QProgressBar = None - dna: DNA = None - - def __init__(self, parent: QWidget = None) -> None: - super().__init__(parent) - self.body: QVBoxLayout = None - self.header: QHBoxLayout = None - self.build_options: QWidget = None - self.extra_build_options: QWidget = None - self.setup_window() - self.create_ui() - - def setup_window(self) -> None: - self.setWindowFlags( - self.windowFlags() - | Qt.WindowTitleHint - | Qt.WindowMaximizeButtonHint - | Qt.WindowMinimizeButtonHint - | Qt.WindowCloseButtonHint - ) - self.setAttribute(Qt.WA_DeleteOnClose) - self.setObjectName(WINDOW_OBJECT) - self.setWindowTitle(WINDOW_TITLE) - self.setWindowFlags(Qt.Window) - self.setFocusPolicy(Qt.StrongFocus) - - def create_ui(self) -> None: - self.main_widget = self.create_main_widget() - self.setCentralWidget(self.main_widget) - self.set_size() - self.setStyleSheet(self.load_css()) - - def load_css(self) -> str: - css = os.path.join(os.path.dirname(__file__), "app.css") - with open(css, encoding="utf-8") as file: - return file.read() - - def create_main_widget(self) -> QWidget: - widget = QWidget() - layout = QVBoxLayout(widget) - - header = self.create_header() - layout.addLayout(header) - layout.addWidget(QHLine()) - - body = self.create_body() - layout.addLayout(body) - - layout.setContentsMargins(MARGIN_LEFT, MARGIN_TOP, MARGIN_RIGHT, MARGIN_BOTTOM) - layout.setSpacing(SPACING) - return widget - - def set_size(self) -> None: - self.setMaximumSize(WINDOW_SIZE_WIDTH_MAX, WINDOW_SIZE_HEIGHT_MAX) - self.setMinimumSize(WINDOW_SIZE_WIDTH_MIN, WINDOW_SIZE_HEIGHT_MIN) - self.resize(WINDOW_SIZE_WIDTH_MIN, WINDOW_SIZE_HEIGHT_MIN) - - def show_message_dialog(self) -> bool: - dlg = QMessageBox() - dlg.setIcon(QMessageBox.Warning) - dlg.setWindowTitle("Warning") - dlg.setText( - "Unsaved changes exists.\nSave changes and create new scene, discard changes, and create new scene or cancel procesing." - ) - dlg.setStandardButtons( - QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel - ) - button = dlg.exec_() - - if button == QMessageBox.Save: - cmds.SaveScene() - return not cmds.file(q=True, modified=True) - if button == QMessageBox.Cancel: - return False - - return True - - def process(self) -> None: - process = True - if cmds.file(q=True, modified=True): - process = self.show_message_dialog() - - if process: - self.set_progress(text="Processing in progress...", value=0) - config = RigConfig( - meshes=self.mesh_tree_list.get_selected_meshes(), - gui_path=GUI_PATH, - analog_gui_path=ANALOG_GUI_PATH, - aas_path=ADDITIONAL_ASSEMBLE_SCRIPT_PATH, - add_rig_logic=self.add_rig_logic(), - add_joints=self.add_joints(), - add_blend_shapes=self.add_blend_shapes(), - add_skin_cluster=self.add_skin_cluster(), - add_ctrl_attributes_on_root_joint=self.add_ctrl_attributes_on_root_joint(), - add_animated_map_attributes_on_root_joint=self.add_animated_map_attributes_on_root_joint(), - add_mesh_name_to_blend_shape_channel_name=self.add_mesh_name_to_blend_shape_channel_name(), - add_key_frames=self.add_key_frames(), - ) - - self.main_widget.setEnabled(False) - try: - self.set_progress(value=33) - self.dna = DNA(self.select_dna_path.get_file_path()) - self.set_progress(value=66) - build_rig(dna=self.dna, config=config) - self.set_progress(text="Processing completed", value=100) - except Exception as e: - self.set_progress(text="Processing failed", value=100) - logging.error(e) - confirmDialog(message=e, button=["ok"], icon="critical") - - self.main_widget.setEnabled(True) - - def set_progress(self, text: str = None, value: int = None) -> None: - if text is not None: - self.progress_bar.setFormat(text) - if value is not None: - self.progress_bar.setValue(value) - - @staticmethod - def show_window() -> None: - if DnaViewerWindow._instance is None: - DnaViewerWindow._instance = DnaViewerWindow( - parent=DnaViewerWindow.maya_main_window() - ) - DnaViewerWindow.activate_window() - - @staticmethod - def maya_main_window() -> QWidget: - for obj in QApplication.topLevelWidgets(): - if obj.objectName() == "MayaWindow": - return obj - raise RuntimeError("Could not find MayaWindow instance") - - @staticmethod - def activate_window() -> None: - try: - DnaViewerWindow._instance.show() - - if DnaViewerWindow._instance.windowState() & Qt.WindowMinimized: - DnaViewerWindow._instance.setWindowState(Qt.WindowActive) - - DnaViewerWindow._instance.raise_() - DnaViewerWindow._instance.activateWindow() - except RuntimeError as e: - logging.info(e) - if str(e).rstrip().endswith("already deleted."): - DnaViewerWindow._instance = None - DnaViewerWindow.show_window() - - def add_joints(self) -> bool: - return self.is_checked(self.joints_cb) - - def add_blend_shapes(self) -> bool: - return self.is_checked(self.blend_shapes_cb) - - def add_skin_cluster(self) -> bool: - return self.is_checked(self.skin_cb) - - def add_rig_logic(self) -> bool: - return self.is_checked(self.rig_logic_cb) - - def add_ctrl_attributes_on_root_joint(self) -> bool: - return self.is_checked(self.ctrl_attributes_on_root_joint_cb) - - def add_animated_map_attributes_on_root_joint(self) -> bool: - return self.is_checked(self.animated_map_attributes_on_root_joint_cb) - - def add_mesh_name_to_blend_shape_channel_name(self) -> bool: - return self.is_checked(self.mesh_name_to_blend_shape_channel_name_cb) - - def add_key_frames(self) -> bool: - return self.is_checked(self.key_frames_cb) - - def is_checked(self, checkbox: QCheckBox) -> bool: - return ( - checkbox is not None - and bool(checkbox.isEnabled()) - and checkbox.checkState() == Qt.CheckState.Checked - ) - - def create_body(self) -> QVBoxLayout: - """Create body layout""" - self.body = QVBoxLayout() - self.body.setContentsMargins( - MARGIN_BODY_LEFT, - MARGIN_BODY_TOP, - MARGIN_BODY_RIGHT, - MARGIN_BOTTOM, - ) - self.body.setSpacing(SPACING) - - # Add DNA browser - self.dna_browser = DNABrowser(DNA_PATH, IMG_PATH, self) - self.dna_browser.dna_selected.connect(self.on_dna_browser_selected) - self.body.addWidget(self.dna_browser) - - # DNA selector - self.create_dna_selector() - - self.mesh_tree_list = self.create_mesh_selector() - - self.build_options = self.create_build_options() - self.extra_build_options = self.create_extra_build_options() - tab = QTabWidget(self) - tab.addTab(self.build_options, "Build options") - tab.addTab(self.extra_build_options, "Extra options") - - widget = QWidget() - layout = QHBoxLayout(widget) - layout.addWidget(tab) - self.body.addWidget(widget) - - self.select_gui_path = FileChooser("", "", self) - self.select_gui_path.fc_text_field.setText(GUI_PATH) - self.select_gui_path.hide() - - self.select_analog_gui_path = FileChooser("", "", self) - self.select_analog_gui_path.fc_text_field.setText(ANALOG_GUI_PATH) - self.select_analog_gui_path.hide() - - self.select_aas_path = FileChooser("", "", self) - self.select_aas_path.fc_text_field.setText(ADDITIONAL_ASSEMBLE_SCRIPT_PATH) - self.select_aas_path.hide() - - self.process_btn = self.create_process_btn() - self.progress_bar = self.create_progress_bar() - - return self.body - - def on_dna_browser_selected(self, dna_path: str) -> None: - """When DNA browser selects a DNA file, update the input field""" - if self.select_dna_path: - self.select_dna_path.fc_text_field.setText(dna_path) - self.on_dna_selected(self.select_dna_path) - - def on_dna_path_changed(self, text: str) -> None: - """When DNA file input field content changes""" - if os.path.exists(text) and text.endswith('.dna'): - self.on_dna_selected(self.select_dna_path) - - def create_header(self) -> QHBoxLayout: - self.header = QHBoxLayout() - - label = QLabel("v" + __version__) - - btn = self.create_help_btn() - - self.header.addWidget(label) - self.header.addStretch(1) - self.header.addWidget(btn) - - self.header.setContentsMargins( - MARGIN_HEADER_LEFT, - MARGIN_HEADER_TOP, - MARGIN_HEADER_RIGHT, - MARGIN_HEADER_BOTTOM, - ) - self.header.setSpacing(SPACING) - return self.header - - def create_help_btn(self) -> QWidget: - btn = QPushButton(self) - btn.setText(" ? ") - btn.setToolTip("Help") - btn.clicked.connect(self.on_help) - return btn - - def on_help(self) -> None: - if TOOL_HELP_URL: - webbrowser.open(TOOL_HELP_URL) - else: - QMessageBox.about( - self, - "About", - "Sorry, this application does not have documentation yet.", - ) - - def create_dna_selector(self) -> QWidget: - widget = QWidget() - self.select_dna_path = self.create_dna_chooser() - self.load_dna_btn = self.create_load_dna_button(self.select_dna_path) - - self.select_dna_path.fc_text_field.textChanged.connect( - lambda: self.on_dna_selected(self.select_dna_path) - ) - - layout = QVBoxLayout() - layout.addWidget(self.select_dna_path) - layout.addWidget(self.load_dna_btn) - layout.setContentsMargins( - MARGIN_HEADER_LEFT, - MARGIN_HEADER_TOP, - MARGIN_HEADER_RIGHT, - MARGIN_HEADER_BOTTOM, - ) - widget.setLayout(layout) - - self.body.addWidget(widget) - - return widget - - def on_dna_selected(self, input: FileChooser) -> None: - enabled = input.get_file_path() is not None - self.load_dna_btn.setEnabled(enabled) - self.process_btn.setEnabled(False) - - def create_dna_chooser(self) -> FileChooser: - return self.create_file_chooser( - "DNA File:", - "DNA file to load. Required by all gui elements", - "Select a DNA file", - "DNA files (*.dna)", - self.on_dna_changed, - ) - - def on_dna_changed(self, state: int) -> None: # pylint: disable=unused-argument - enabled = False - if self.dna: - if self.dna.path == self.select_dna_path.get_file_path(): - enabled = True - - self.load_dna_btn.setEnabled(enabled) - self.mesh_tree_list.btn_select_all.setEnabled(enabled) - self.mesh_tree_list.btn_deselect_all.setEnabled(enabled) - self.process_btn.setEnabled(enabled) - - def create_load_dna_button(self, dna_input: FileChooser) -> QWidget: - btn = QPushButton("Load DNA") - btn.setEnabled(False) - btn.clicked.connect(lambda: self.on_load_dna_clicked(dna_input)) - return btn - - def on_load_dna_clicked(self, input: FileChooser) -> None: - self.main_widget.setEnabled(False) - QCoreApplication.processEvents() - try: - dna_file_path = input.get_file_path() - - if dna_file_path: - self.dna = DNA(dna_file_path, [Layer.definition]) - lod_count = self.dna.get_lod_count() - names = self.get_mesh_names() - indices_names = self.get_lod_indices_names() - self.mesh_tree_list.fill_mesh_list(lod_count, names, indices_names) - self.joints_cb.setEnabled(True) - self.enable_additional_build_options(True) - self.process_btn.setEnabled(False) - self.mesh_tree_list.btn_select_all.setEnabled(True) - self.mesh_tree_list.btn_deselect_all.setEnabled(True) - except Exception as e: - dlg = QMessageBox() - dlg.setIcon(QMessageBox.Warning) - dlg.setWindowTitle("Error") - dlg.setText(str(e)) - dlg.setStandardButtons(QMessageBox.Ok) - dlg.exec_() - - self.main_widget.setEnabled(True) - - def get_mesh_names(self) -> List[str]: - names: List[str] = [] - for index in range(self.dna.get_mesh_count()): - names.append(self.dna.get_mesh_name(index)) - return names - - def get_lod_indices_names(self) -> List[List[int]]: - lod_indices: List[List[int]] = [] - for index in range(self.dna.get_lod_count()): - lod_indices.append(self.dna.get_mesh_indices_for_lod(index)) - return lod_indices - - def create_mesh_selector(self) -> MeshTreeList: - widget = MeshTreeList(self) - self.body.addWidget(widget) - return widget - - def create_file_chooser( - self, - label: str, - hint: str, - caption: str, - filter: str, - on_changed: Callable[[int], None] = None, - ) -> FileChooser: - - widget = FileChooser( - label, - hint, - self, - dialog_caption=caption, - dialog_filter=filter, - on_changed=on_changed or self.on_generic_changed, - ) - self.body.addWidget(widget) - return widget - - def create_gui_selector(self) -> FileChooser: - return self.create_file_chooser( - "Gui path:", - "GUI file to load. Required by RigLogic", - "Select the gui file", - "gui files (*.ma)", - ) - - def create_aas_selector(self) -> FileChooser: - return self.create_file_chooser( - "Additional assemble script path:", - "Additional assemble script to use. Required by RigLogic", - "Select the aas file", - "python script (*.py)", - ) - - def create_analog_gui_selector(self) -> FileChooser: - return self.create_file_chooser( - "Analog gui path:", - "Analog GUI file to load. Required by RigLogic", - "Select the analog gui file", - "analog gui files (*.ma)", - ) - - def create_build_options(self) -> QWidget: - widget = QWidget() - layout = QVBoxLayout(widget) - layout.setContentsMargins( - MARGIN_BODY_LEFT, - MARGIN_BODY_TOP, - MARGIN_BODY_RIGHT, - MARGIN_BOTTOM, - ) - - self.joints_cb = self.create_checkbox( - "joints", - "Add joints to rig. Requires: DNA to be loaded", - layout, - self.on_joints_changed, - ) - self.blend_shapes_cb = self.create_checkbox( - "blend shapes", - "Add blend shapes to rig. Requires: DNA to be loaded and at least one mesh to be check", - layout, - self.on_generic_changed, - ) - self.skin_cb = self.create_checkbox( - "skin cluster", - "Add skin cluster to rig. Requires: DNA to be loaded and at least one mesh and joints to be checked", - layout, - self.on_generic_changed, - ) - self.rig_logic_cb = self.create_checkbox( - "rig logic", - "Add RigLogic to rig. Requires: DNA to be loaded, all meshes to be checked, joints, skin, blend shapes to be checked, also gui, analog gui and additional assemble script must be set", - layout, - ) - layout.addStretch() - - widget.setMaximumHeight(150) - return widget - - def create_extra_build_options(self) -> QWidget: - widget = QWidget() - layout = QVBoxLayout(widget) - layout.setContentsMargins( - MARGIN_BODY_LEFT, - MARGIN_BODY_TOP, - MARGIN_BODY_RIGHT, - MARGIN_BOTTOM, - ) - - self.ctrl_attributes_on_root_joint_cb = self.create_checkbox( - "ctrl attributes on root joint", - "ctrl attributes on root joint", - layout, - enabled=True, - checked=True, - ) - self.animated_map_attributes_on_root_joint_cb = self.create_checkbox( - "animated map attributes on root joint", - "animated map attributes on root joint", - layout, - enabled=True, - checked=True, - ) - self.mesh_name_to_blend_shape_channel_name_cb = self.create_checkbox( - "mesh name to blend shape channel name", - "mesh name to blend shape channel name", - layout, - enabled=True, - checked=True, - ) - self.key_frames_cb = self.create_checkbox( - "key frames", - "Add keyframes to rig", - layout, - enabled=True, - checked=True, - ) - layout.addStretch() - - widget.setMaximumHeight(150) - return widget - - def enable_additional_build_options(self, enable: bool) -> None: - self.ctrl_attributes_on_root_joint_cb.setEnabled(enable) - self.animated_map_attributes_on_root_joint_cb.setEnabled(enable) - self.mesh_name_to_blend_shape_channel_name_cb.setEnabled(enable) - self.key_frames_cb.setEnabled(enable) - - def create_checkbox( - self, - label: str, - hint: str, - layout: QHBoxLayout, - on_changed: Callable[[int], None] = None, - checked: bool = False, - enabled: bool = False, - ) -> QCheckBox: - - checkbox = QCheckBox(label, self) - checkbox.setChecked(checked) - checkbox.setEnabled(enabled) - checkbox.setToolTip(hint) - if on_changed: - checkbox.stateChanged.connect(on_changed) - layout.addWidget(checkbox) - return checkbox - - def on_joints_changed(self, state: int) -> None: - if self.joints_cb.isChecked(): - self.process_btn.setEnabled(True) - if self.mesh_tree_list.get_selected_meshes(): - self.skin_cb.setEnabled(True) - else: - self.skin_cb.setEnabled(False) - if not self.mesh_tree_list.get_selected_meshes(): - self.process_btn.setEnabled(False) - self.on_generic_changed(state) - - def create_process_btn(self) -> QPushButton: - btn = QPushButton("Process") - btn.setEnabled(False) - btn.clicked.connect(self.process) - - self.body.addWidget(btn) - return btn - - def create_progress_bar(self) -> QProgressBar: - progress = QProgressBar(self) - progress.setRange(0, 100) - progress.setValue(0) - progress.setTextVisible(True) - progress.setFormat("") - self.body.addWidget(progress) - return progress - - def on_generic_changed(self, state: int) -> None: # pylint: disable=unused-argument - self.set_riglogic_cb_enabled() - - def is_enabled_and_checked(self, check_box: QCheckBox) -> bool: - return ( - check_box is not None - and bool(check_box.isEnabled()) - and bool(check_box.isChecked()) - ) - - def set_riglogic_cb_enabled(self) -> None: - all_total_meshes = False - - if self.dna and self.is_enabled_and_checked(self.blend_shapes_cb): - if len(self.mesh_tree_list.get_selected_meshes()) == self.dna.get_mesh_count(): - all_total_meshes = True - - enabled = ( - self.is_enabled_and_checked(self.joints_cb) - and self.is_enabled_and_checked(self.blend_shapes_cb) - and all_total_meshes - and self.is_enabled_and_checked(self.skin_cb) - ) - self.rig_logic_cb.setEnabled(enabled) \ No newline at end of file diff --git a/scripts/dna_viewer/ui/widgets.py b/scripts/dna_viewer/ui/widgets.py deleted file mode 100644 index 1cb1a56..0000000 --- a/scripts/dna_viewer/ui/widgets.py +++ /dev/null @@ -1,111 +0,0 @@ -from pathlib import Path -from typing import Callable, Optional - -from PySide2.QtCore import Qt -from PySide2.QtWidgets import ( - QFileDialog, - QFrame, - QHBoxLayout, - QLabel, - QLineEdit, - QPushButton, - QWidget, -) - - -class QLine(QFrame): - """A widget for creating a horizontal line""" - - def __init__(self, line: QFrame.Shape) -> None: - super().__init__() - self.setFrameShape(line) - self.setFrameShadow(QFrame.Sunken) - - -class QHLine(QLine): - """A widget for creating a horizontal line""" - - def __init__(self) -> None: - super().__init__(QFrame.HLine) - - -class FileChooser(QWidget): - """ - A custom widget used for selecting a file path using a FileDialog and an input field - """ - - def __init__( - self, - label_text: str, - hint: str, - parent: Optional[QWidget] = None, - placeholder: str = "", - dialog_caption: str = "Select a file", - dialog_filter: str = "All files (*.*)", - button_text: str = "...", - dir_selector: bool = False, - on_changed: Callable[[int], None] = None, - ) -> None: - super().__init__(parent=parent) - - self._dialog_caption = dialog_caption - self._dialog_filter = dialog_filter - self._dir_selector = dir_selector - - layout = QHBoxLayout() - layout.setMargin(0) - - fc_label = QLabel(label_text) - fc_label.setMinimumHeight(32) - fc_label.setToolTip(hint) - - self.fc_text_field = QLineEdit() - self.fc_text_field.setAlignment(Qt.AlignLeft) - self.fc_text_field.setPlaceholderText(placeholder) - self.fc_text_field.textChanged.connect(on_changed) - self.fc_text_field.setToolTip(hint) - - fc_btn = QPushButton(button_text) - fc_btn.setToolTip(hint) - - layout.addWidget(fc_label) - layout.addWidget(self.fc_text_field) - layout.addWidget(fc_btn) - - fc_btn.clicked.connect( - self.open_dialog, - ) - - self.setLayout(layout) - - def get_file_path(self) -> str: - """ - Gets the file path from the text field - - @rtype: str - @returns: The file path contained in the text field - """ - - path = str(self.fc_text_field.text()) - if path and Path(path.strip()).exists(): - return path - return None - - def open_dialog(self) -> None: - """Opens a file dialog, when a path is chosen, the text field gets filled with its value""" - - if self._dir_selector: - file_name, _ = QFileDialog.getExistingDirectory( - self, - self._dialog_caption, - "", - QFileDialog.Option.ShowDirsOnly, - ) - if file_name: - self.fc_text_field.setText(file_name) - else: - file_name, _ = QFileDialog.getOpenFileName( - self, self._dialog_caption, "", self._dialog_filter - ) - if file_name: - self.fc_text_field.setText(file_name) diff --git a/scripts/dna_viewer/version.py b/scripts/dna_viewer/version.py deleted file mode 100644 index 58039f5..0000000 --- a/scripts/dna_viewer/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "2.1.1"