├── .gitmodules ├── polySymmetry.mod ├── polySymmetry ├── plug-ins │ ├── linux-maya2014 │ │ └── polySymmetry.so │ ├── linux-maya2015 │ │ └── polySymmetry.so │ ├── linux-maya2016 │ │ └── polySymmetry.so │ ├── linux-maya2017 │ │ └── polySymmetry.so │ ├── windows-maya2014 │ │ └── polySymmetry.mll │ ├── windows-maya2015 │ │ └── polySymmetry.mll │ ├── windows-maya2016 │ │ └── polySymmetry.mll │ └── windows-maya2017 │ │ └── polySymmetry.mll └── scripts │ └── polySymmetry │ ├── tools.py │ ├── utils.py │ ├── __init__.py │ ├── commands.py │ └── options.py ├── src ├── util.h ├── parseArgs.h ├── polyChecksum.h ├── util.cpp ├── polyChecksum.cpp ├── polyChecksumCommand.h ├── polyMirrorCmd.h ├── sceneCache.h ├── selection.h ├── polyFlipCmd.h ├── parseArgs.cpp ├── meshData.h ├── polyChecksumCommand.cpp ├── polyDeformerWeights.h ├── polySymmetry.h ├── polySymmetryTool.h ├── polySymmetryNode.h ├── polySymmetryCmd.h ├── pluginMain.cpp ├── polyMirrorCmd.cpp ├── polySkinWeights.h ├── sceneCache.cpp ├── meshData.cpp ├── polyFlipCmd.cpp ├── selection.cpp ├── polySymmetryNode.cpp ├── polySymmetryTool.cpp ├── polyDeformerWeights.cpp ├── polySymmetry.cpp ├── polySymmetryCmd.cpp └── polySkinWeights.cpp ├── README.md ├── CMakeLists.txt └── LICENSE /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pystring"] 2 | path = pystring 3 | url = https://github.com/imageworks/pystring.git 4 | -------------------------------------------------------------------------------- /polySymmetry.mod: -------------------------------------------------------------------------------- 1 | + polySymmetry 0.5 ${MAYA_MODULE_PATH}/polySymmetry 2 | [r] scripts: scripts 3 | plug-ins: plug-ins -------------------------------------------------------------------------------- /polySymmetry/plug-ins/linux-maya2014/polySymmetry.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yantor3d/polySymmetry/HEAD/polySymmetry/plug-ins/linux-maya2014/polySymmetry.so -------------------------------------------------------------------------------- /polySymmetry/plug-ins/linux-maya2015/polySymmetry.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yantor3d/polySymmetry/HEAD/polySymmetry/plug-ins/linux-maya2015/polySymmetry.so -------------------------------------------------------------------------------- /polySymmetry/plug-ins/linux-maya2016/polySymmetry.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yantor3d/polySymmetry/HEAD/polySymmetry/plug-ins/linux-maya2016/polySymmetry.so -------------------------------------------------------------------------------- /polySymmetry/plug-ins/linux-maya2017/polySymmetry.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yantor3d/polySymmetry/HEAD/polySymmetry/plug-ins/linux-maya2017/polySymmetry.so -------------------------------------------------------------------------------- /polySymmetry/plug-ins/windows-maya2014/polySymmetry.mll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yantor3d/polySymmetry/HEAD/polySymmetry/plug-ins/windows-maya2014/polySymmetry.mll -------------------------------------------------------------------------------- /polySymmetry/plug-ins/windows-maya2015/polySymmetry.mll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yantor3d/polySymmetry/HEAD/polySymmetry/plug-ins/windows-maya2015/polySymmetry.mll -------------------------------------------------------------------------------- /polySymmetry/plug-ins/windows-maya2016/polySymmetry.mll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yantor3d/polySymmetry/HEAD/polySymmetry/plug-ins/windows-maya2016/polySymmetry.mll -------------------------------------------------------------------------------- /polySymmetry/plug-ins/windows-maya2017/polySymmetry.mll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yantor3d/polySymmetry/HEAD/polySymmetry/plug-ins/windows-maya2017/polySymmetry.mll -------------------------------------------------------------------------------- /polySymmetry/scripts/polySymmetry/tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | polySymmetry 3 | 4 | Support script for polySymmetry plugin. 5 | """ 6 | 7 | from maya import cmds 8 | 9 | def polySymmetryTool(*args): 10 | cmds.setToolTo(cmds.polySymmetryCtx()) -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef POLY_SYMMETRY_UTIL_H 7 | #define POLY_SYMMETRY_UTIL_H 8 | 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | vector intersection(vector &a, vector &b); 15 | bool contains(vector &items, int &value); 16 | 17 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### polySymmetry 2 | Maya plugin with tools that operate on meshes using symmetry tables calculated by topology. Requires Maya 2016 or later version. 3 | 4 | #### Description 5 | See the [Wiki](https://github.com/yantor3d/polySymmetry/wiki) for full details. 6 | 7 | ## Plugin Contents 8 | ### Tools 9 | - polySymmetryCtx 10 | 11 | ### Commands 12 | - polyDeformerWeights 13 | - polyFlip 14 | - polyMirror 15 | - polySkinWeights 16 | - polySymmetry 17 | 18 | ### Nodes 19 | - polySymmetryData 20 | -------------------------------------------------------------------------------- /src/parseArgs.h: -------------------------------------------------------------------------------- 1 | #ifndef PARSE_ARGS_H 2 | #define PARSE_ARGS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace parseArgs 11 | { 12 | MStatus getNodeArgument(MArgDatabase &argsData, const char* flag, MObject &node, bool required); 13 | MStatus getDagPathArgument(MArgDatabase &argsData, const char* flag, MDagPath &path, bool required); 14 | 15 | bool isNodeType(MObject &node, MFn::Type nodeType); 16 | bool isNodeType(MDagPath &path, MFn::Type nodeType); 17 | } 18 | 19 | #endif -------------------------------------------------------------------------------- /src/polyChecksum.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef POLY_CHECKSUM_H 7 | #define POLY_CHECKSUM_H 8 | 9 | #include 10 | 11 | // based on code found at http://www.relisoft.com/science/CrcOptim.html 12 | 13 | class PolyChecksum 14 | { 15 | public: 16 | PolyChecksum(); 17 | virtual void putBytes(void* bytes, size_t dataSize); 18 | virtual int getResult(); 19 | 20 | public: 21 | unsigned long _table[256]; 22 | unsigned long _register = 0; 23 | unsigned long _key = 0x04c11db7; 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/util.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include "util.h" 7 | 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | vector intersection(vector &a, vector &b) 14 | { 15 | vector result(a.size() + b.size()); 16 | vector::iterator it; 17 | 18 | it = set_intersection( 19 | a.begin(), 20 | a.end(), 21 | b.begin(), 22 | b.end(), 23 | result.begin() 24 | ); 25 | 26 | result.resize(it - result.begin()); 27 | 28 | return result; 29 | } 30 | 31 | bool contains(vector &items, int &value) 32 | { 33 | return find(items.begin(), items.end(), value) != items.end(); 34 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | 3 | # Download Chad Vernon's cgcmake package (https://github.com/chadmv/cgcmake/) 4 | # and make sure your CMAKE_MODULES_PATH environment variable points at it. 5 | 6 | set(CMAKE_MODULE_PATH "$ENV{CMAKE_MODULE_PATH}") 7 | 8 | project(polySymmetry) 9 | file(GLOB SOURCE_FILES "src/*.cpp" "src/*.h" "pystring/pystring.*") 10 | 11 | find_package(Maya REQUIRED) 12 | 13 | if (WIN32) 14 | elseif(APPLE) 15 | else() 16 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 17 | endif() 18 | 19 | include_directories(${MAYA_INCLUDE_DIR}) 20 | link_directories(${MAYA_LIBRARY_DIR}) 21 | 22 | add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) 23 | target_link_libraries(${PROJECT_NAME} ${MAYA_LIBRARIES}) 24 | 25 | MAYA_PLUGIN(${PROJECT_NAME}) 26 | 27 | -------------------------------------------------------------------------------- /src/polyChecksum.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include "polyChecksum.h" 7 | 8 | PolyChecksum::PolyChecksum() 9 | { 10 | // for all possible byte values 11 | for (unsigned i = 0; i < 256; ++i) 12 | { 13 | unsigned long reg = i << 24; 14 | // for all bits in a byte 15 | for (int j = 0; j < 8; ++j) 16 | { 17 | bool topBit = (reg & 0x80000000) != 0; 18 | reg <<= 1; 19 | 20 | if (topBit) 21 | reg ^= _key; 22 | } 23 | _table [i] = reg; 24 | } 25 | } 26 | 27 | void PolyChecksum::putBytes(void* bytes, size_t dataSize) 28 | { 29 | unsigned char* ptr = (unsigned char*) bytes; 30 | 31 | for (size_t i = 0; i < dataSize; i++) 32 | { 33 | unsigned byte = *(ptr + i); 34 | unsigned top = _register >> 24; 35 | top ^= byte; 36 | top &= 255; 37 | 38 | _register = (_register << 8) ^ _table [top]; 39 | } 40 | } 41 | 42 | int PolyChecksum::getResult() 43 | { 44 | return (int) this->_register; 45 | } 46 | -------------------------------------------------------------------------------- /src/polyChecksumCommand.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef TOPOLOGY_CHECKSUM_COMMAND_H 7 | #define TOPOLOGY_CHECKSUM_COMMAND_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | class PolyChecksumCommand : public MPxCommand 18 | { 19 | public: 20 | PolyChecksumCommand(); 21 | virtual ~PolyChecksumCommand(); 22 | 23 | static void* creator(); 24 | static MSyntax getSyntax(); 25 | 26 | virtual MStatus doIt(const MArgList& argList); 27 | virtual MStatus redoIt(); 28 | 29 | virtual bool isUndoable() const { return false; } 30 | virtual bool hasSyntax() const { return true; } 31 | 32 | public: 33 | static MString COMMAND_NAME; 34 | 35 | private: 36 | MDagPath mesh; 37 | }; 38 | 39 | #endif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ryan Porter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /polySymmetry/scripts/polySymmetry/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import maya.cmds as cmds 4 | 5 | 6 | class InvalidSelection(ValueError): 7 | pass 8 | 9 | 10 | def loadOptions(commandName): 11 | result = {} 12 | 13 | varName = "{}OptionVar".format(commandName) 14 | 15 | if cmds.optionVar(exists=varName): 16 | options = cmds.optionVar(query=varName) 17 | 18 | try: 19 | result = json.loads(options) 20 | except (ValueError, TypeError) as e: 21 | cmds.warning("Error loading options for '{}' - {}".format(commandName, e)) 22 | 23 | 24 | return result 25 | 26 | 27 | def deleteOptions(commandName): 28 | varName = "{}OptionVar".format(commandName) 29 | 30 | if cmds.optionVar(exists=varName): 31 | cmds.optionVar(remove=varName) 32 | 33 | 34 | def saveOptions(commandName, options): 35 | varName = "{}OptionVar".format(commandName) 36 | 37 | try: 38 | stringVal = json.dumps(options) 39 | except (ValueError, TypeError) as e: 40 | cmds.warning("Error saving options for '{}' - {}".format(commandName, e)) 41 | else: 42 | cmds.optionVar(stringValue=(varName, stringVal)) -------------------------------------------------------------------------------- /src/polyMirrorCmd.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef POLY_MIRROR_COMMAND_H 7 | #define POLY_MIRROR_COMMAND_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | class PolyMirrorCommand : public MPxCommand 20 | { 21 | public: 22 | PolyMirrorCommand(); 23 | virtual ~PolyMirrorCommand(); 24 | 25 | static void* creator(); 26 | static MSyntax getSyntax(); 27 | 28 | virtual MStatus doIt(const MArgList& argList); 29 | virtual MStatus redoIt(); 30 | virtual MStatus undoIt(); 31 | 32 | virtual bool isUndoable() const { return true; } 33 | virtual bool hasSyntax() const { return true; } 34 | 35 | public: 36 | static MString COMMAND_NAME; 37 | 38 | private: 39 | MPointArray originalPoints; 40 | MObject polySymmetryData; 41 | 42 | MDagPath baseMesh; 43 | MDagPath targetMesh; 44 | }; 45 | #endif 46 | -------------------------------------------------------------------------------- /src/sceneCache.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef POLY_SYMMETRY_SCENE_CACHE_H 7 | #define POLY_SYMMETRY_SCENE_CACHE_H 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | 19 | class PolySymmetryCache 20 | { 21 | public: 22 | static MStatus initialize(); 23 | static MStatus uninitialize(); 24 | 25 | static void sceneUpdateCallback(void* clientData); 26 | 27 | static void nodeAddedCallback(MObject &node, void* clientData); 28 | static void nodeRemovedCallback(MObject &node, void* clientData); 29 | 30 | static void newFileCallback(void* clientData); 31 | 32 | static void beforeOpenFileCallback(void* clientData); 33 | static void afterOpenFileCallback(void* clientData); 34 | 35 | static void addNodeToCache(MObject &node); 36 | static bool getNodeFromCache(MDagPath &mesh, MObject &node); 37 | 38 | public: 39 | static unordered_map symmetryNodeCache; 40 | 41 | static MCallbackIdArray callbackIDs; 42 | static bool cacheNodes; 43 | }; 44 | 45 | #endif -------------------------------------------------------------------------------- /src/selection.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef POLY_SYMMETRY_SELECTION_H 7 | #define POLY_SYMMETRY_SELECTION_H 8 | 9 | #include "meshData.h" 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | 20 | /* 21 | User selection of the symmetrical components on a shell, with a 22 | stand-alone vertex on the left side of the shell. 23 | 24 | The first edge index is connected to the first face index. 25 | The first vertex index is connected to the first edge index. 26 | */ 27 | struct ComponentSelection 28 | { 29 | pair edgeIndices; 30 | pair faceIndices; 31 | pair vertexIndices; 32 | 33 | int leftVertexIndex = -1; 34 | 35 | ComponentSelection() {} 36 | }; 37 | 38 | void getSelectedComponents(MDagPath &selectedMesh, MSelectionList &activeSelection, MSelectionList &selection, MFn::Type componentType); 39 | void getSelectedComponentIndices(MSelectionList &activeSelection, vector &indices, MFn::Type componentType); 40 | bool getSymmetricalComponentSelection(MeshData &meshData, MSelectionList &selection, ComponentSelection &componentSelection, bool leftSideVertexSelected); 41 | void getAllVertices(int &numberOfVertices, MObject &components); 42 | 43 | #endif -------------------------------------------------------------------------------- /src/polyFlipCmd.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef POLY_FLIP_CMD_H 7 | #define POLY_FLIP_CMD_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | using namespace std; 20 | 21 | class PolyFlipCommand : public MPxCommand 22 | { 23 | public: 24 | PolyFlipCommand(); 25 | virtual ~PolyFlipCommand(); 26 | 27 | static void* creator(); 28 | static MSyntax getSyntax(); 29 | 30 | virtual MStatus doIt(const MArgList& argList); 31 | virtual MStatus redoIt(); 32 | virtual MStatus undoIt(); 33 | 34 | virtual MStatus flipMesh(); 35 | virtual MStatus flipMeshAgainst(); 36 | 37 | virtual bool isUndoable() const { return true; } 38 | virtual bool hasSyntax() const { return true; } 39 | 40 | public: 41 | static MString COMMAND_NAME; 42 | 43 | private: 44 | bool worldSpace = false; 45 | bool objectSpace = true; 46 | 47 | MPointArray originalPoints; 48 | MObject polySymmetryData; 49 | MDagPath selectedMesh; 50 | MDagPath referenceMesh; 51 | }; 52 | 53 | #endif -------------------------------------------------------------------------------- /src/parseArgs.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include "parseArgs.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | MStatus parseArgs::getNodeArgument(MArgDatabase &argsData, const char* flag, MObject &node, bool required) 17 | { 18 | MStatus status; 19 | 20 | if (argsData.isFlagSet(flag)) 21 | { 22 | MSelectionList selection; 23 | MString objectName; 24 | 25 | argsData.getFlagArgument(flag, 0, objectName); 26 | selection.add(objectName); 27 | selection.getDependNode(0, node); 28 | } else if (required) { 29 | MString errorMsg("The ^1s flag is required."); 30 | errorMsg.format(errorMsg, MString(flag)); 31 | MGlobal::displayError(errorMsg); 32 | return MStatus::kFailure; 33 | } 34 | 35 | return MStatus::kSuccess; 36 | } 37 | 38 | MStatus parseArgs::getDagPathArgument(MArgDatabase &argsData, const char* flag, MDagPath &path, bool required) 39 | { 40 | MStatus status; 41 | 42 | MObject obj; 43 | status = getNodeArgument(argsData, flag, obj, required); 44 | 45 | if (status) 46 | { 47 | MDagPath::getAPathTo(obj, path); 48 | } 49 | 50 | return MStatus::kSuccess; 51 | } 52 | 53 | bool parseArgs::isNodeType(MObject &node, MFn::Type nodeType) 54 | { 55 | return !node.isNull() && node.hasFn(nodeType); 56 | } 57 | 58 | bool parseArgs::isNodeType(MDagPath &path, MFn::Type nodeType) 59 | { 60 | return path.isValid() && path.hasFn(nodeType); 61 | } -------------------------------------------------------------------------------- /src/meshData.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef MESH_DATA_CMD_H 7 | #define MESH_DATA_CMD_H 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | 19 | struct ComponentData 20 | { 21 | vector connectedVertices; 22 | vector connectedEdges; 23 | vector connectedFaces; 24 | }; 25 | 26 | struct VertexData : ComponentData 27 | { 28 | unordered_map> faceVertexSiblings; 29 | }; 30 | 31 | struct EdgeData : ComponentData {}; 32 | struct FaceData : ComponentData {}; 33 | 34 | class MeshData 35 | { 36 | public: 37 | MeshData(); 38 | virtual ~MeshData(); 39 | 40 | virtual void unpackMesh(MDagPath &meshDagPath); 41 | virtual void clear(); 42 | 43 | static unsigned long getVertexChecksum(MDagPath &meshDagPath); 44 | 45 | private: 46 | 47 | virtual void unpackEdges(MItMeshEdge &edges); 48 | virtual void unpackFaces(MItMeshPolygon &faces); 49 | virtual void unpackVertices(MItMeshVertex &vertices); 50 | virtual void unpackVertexSiblings(); 51 | 52 | virtual void insertAll(MIntArray &src, vector &dest); 53 | 54 | public: 55 | int numberOfVertices = 0; 56 | int numberOfEdges = 0; 57 | int numberOfFaces = 0; 58 | 59 | vector vertexData; 60 | vector edgeData; 61 | vector faceData; 62 | 63 | unsigned long vertexChecksum; 64 | }; 65 | 66 | 67 | #endif -------------------------------------------------------------------------------- /src/polyChecksumCommand.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include "meshData.h" 7 | #include "polyChecksumCommand.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | using namespace std; 23 | 24 | PolyChecksumCommand::PolyChecksumCommand() {} 25 | PolyChecksumCommand::~PolyChecksumCommand() {} 26 | 27 | void* PolyChecksumCommand::creator() 28 | { 29 | return new PolyChecksumCommand(); 30 | } 31 | 32 | MSyntax PolyChecksumCommand::getSyntax() 33 | { 34 | MSyntax syntax; 35 | 36 | syntax.setObjectType(MSyntax::kSelectionList, 1, 1); 37 | syntax.useSelectionAsDefault(true); 38 | 39 | syntax.enableQuery(false); 40 | syntax.enableEdit(false); 41 | 42 | return syntax; 43 | } 44 | 45 | MStatus PolyChecksumCommand::doIt(const MArgList& argList) 46 | { 47 | MStatus status; 48 | MArgDatabase argsData(syntax(), argList); 49 | 50 | MSelectionList selection; 51 | argsData.getObjects(selection); 52 | 53 | status = selection.getDagPath(0, this->mesh); 54 | 55 | if (!status || !mesh.hasFn(MFn::Type::kMesh)) 56 | { 57 | MGlobal::displayError("Must select a mesh."); 58 | return MStatus::kFailure; 59 | } 60 | 61 | return this->redoIt(); 62 | } 63 | 64 | MStatus PolyChecksumCommand::redoIt() 65 | { 66 | unsigned long checksum = MeshData::getVertexChecksum(this->mesh); 67 | this->setResult((int) checksum); 68 | 69 | return MStatus::kSuccess; 70 | } -------------------------------------------------------------------------------- /src/polyDeformerWeights.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef POLY_DEFORMER_WEIGHTS_H 7 | #define POLY_DEFORMER_WEIGHTS_H 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace std; 22 | 23 | class PolyDeformerWeightsCommand : public MPxCommand 24 | { 25 | public: 26 | PolyDeformerWeightsCommand(); 27 | virtual ~PolyDeformerWeightsCommand(); 28 | 29 | static void* creator(); 30 | static MSyntax getSyntax(); 31 | 32 | virtual MStatus parseArguments(MArgDatabase &argsData); 33 | virtual MStatus validateArguments(); 34 | 35 | virtual MStatus doIt(const MArgList& argList); 36 | virtual MStatus redoIt(); 37 | virtual MStatus undoIt(); 38 | 39 | virtual float getWeight(int vertexIndex, MFloatArray &sourceWeights, vector &vertexSymmetry, vector vertexSides); 40 | 41 | virtual bool isUndoable() const { return true; } 42 | virtual bool hasSyntax() const { return true; } 43 | 44 | public: 45 | static MString COMMAND_NAME; 46 | 47 | private: 48 | int direction = 1; 49 | bool mirrorWeights = false; 50 | bool flipWeights = false; 51 | 52 | uint geometryIndex; 53 | 54 | MFloatArray oldWeightValues; 55 | 56 | MDagPath sourceMesh; 57 | MDagPath destinationMesh; 58 | 59 | MObject polySymmetryData; 60 | 61 | MObject sourceDeformer; 62 | MObject destinationDeformer; 63 | MObject components; 64 | }; 65 | 66 | #endif -------------------------------------------------------------------------------- /src/polySymmetry.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef POLY_SYMMETRY_H 7 | #define POLY_SYMMETRY_H 8 | 9 | #include "meshData.h" 10 | #include "selection.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | using namespace std; 19 | 20 | class PolySymmetryData 21 | { 22 | public: 23 | PolySymmetryData(); 24 | ~PolySymmetryData(); 25 | 26 | virtual void clear(); 27 | virtual void reset(); 28 | virtual void initialize(MDagPath &mesh); 29 | 30 | virtual void findSymmetricalVertices(ComponentSelection &selection); 31 | virtual void findFirstSymmetricalVertices(ComponentSelection &selection); 32 | virtual void findVertexSides(vector &leftSideVertexIndices); 33 | virtual void finalizeSymmetry(); 34 | 35 | private: 36 | virtual pair getUnexaminedFaces(pair &edgePair); 37 | virtual int getUnexaminedFace(int &edgeIndex); 38 | virtual int getUnexaminedVertexSibling(int &vertexIndex, int &faceIndex); 39 | 40 | virtual void findSymmetricalVerticesOnFace(pair &facePair); 41 | virtual void findSymmetricalEdgesOnFace(queue> &symmetricalEdgesQueue, int &faceIndex); 42 | 43 | virtual void markSymmetricalVertices(int &i0, int &i1); 44 | virtual void markSymmetricalEdges(int &i0, int &i1); 45 | virtual void markSymmetricalFaces(int &i0, int &i1); 46 | 47 | public: 48 | vector vertexSymmetryIndices; 49 | vector edgeSymmetryIndices; 50 | vector faceSymmetryIndices; 51 | 52 | vector vertexSides; 53 | vector edgeSides; 54 | vector faceSides; 55 | 56 | private: 57 | MeshData meshData; 58 | 59 | vector examinedEdges; 60 | vector examinedFaces; 61 | vector examinedVertices; 62 | 63 | vector leftSideVertexIndices; 64 | }; 65 | 66 | #endif -------------------------------------------------------------------------------- /src/polySymmetryTool.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef POLY_SYMMETRY_TOOL_H 7 | #define POLY_SYMMETRY_TOOL_H 8 | 9 | #include "meshData.h" 10 | #include "polySymmetry.h" 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | using namespace std; 27 | 28 | const int VERTICES = 1; 29 | const int COMPONENTS = 2; 30 | 31 | class PolySymmetryTool : public MPxSelectionContext 32 | { 33 | public: 34 | PolySymmetryTool(); 35 | virtual ~PolySymmetryTool(); 36 | 37 | virtual void toolOnSetup(MEvent &event); 38 | virtual void toolOffCleanup(); 39 | 40 | virtual MStatus helpStateHasChanged(MEvent &event); 41 | 42 | virtual void abortAction(); 43 | virtual void completeAction(); 44 | virtual void deleteAction(); 45 | 46 | virtual void doToolCommand(); 47 | 48 | virtual MStatus getSelectedMesh(); 49 | virtual MStatus clearSelectedMesh(); 50 | 51 | virtual void updateHelpString(); 52 | virtual void recalculateSymmetry(); 53 | virtual void updateDisplayColors(); 54 | 55 | private: 56 | MDagPath selectedMesh; 57 | MeshData meshData; 58 | PolySymmetryData symmetryData; 59 | 60 | MColorArray originalVertexColors; 61 | bool originalDisplayColors = false; 62 | 63 | MDGModifier displayColorModifier; 64 | vector affectedVertices; 65 | 66 | vector selectedComponents; 67 | vector leftSideVertexIndices; 68 | }; 69 | 70 | class PolySymmetryContextCmd : public MPxContextCommand 71 | { 72 | public: 73 | PolySymmetryContextCmd(); 74 | ~PolySymmetryContextCmd(); 75 | 76 | virtual MPxContext* makeObj(); 77 | static void* creator(); 78 | 79 | public: 80 | static MString COMMAND_NAME; 81 | }; 82 | 83 | #endif -------------------------------------------------------------------------------- /src/polySymmetryNode.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef POLY_SYMMETRY_NODE_H 7 | #define POLY_SYMMETRY_NODE_H 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define NUMBER_OF_EDGES "numberOfEdges" 22 | #define NUMBER_OF_FACES "numberOfFaces" 23 | #define NUMBER_OF_VERTICES "numberOfVertices" 24 | 25 | #define VERTEX_CHECKSUM "vertexChecksum" 26 | #define EDGE_SYMMETRY "edgeSymmetry" 27 | #define FACE_SYMMETRY "faceSymmetry" 28 | #define VERTEX_SYMMETRY "vertexSymmetry" 29 | 30 | #define EDGE_SIDES "edgeSides" 31 | #define FACE_SIDES "faceSides" 32 | #define VERTEX_SIDES "vertexSides" 33 | 34 | using namespace std; 35 | 36 | class PolySymmetryNode : MPxNode 37 | { 38 | public: 39 | PolySymmetryNode(); 40 | virtual ~PolySymmetryNode(); 41 | 42 | static void* creator(); 43 | static MStatus initialize(); 44 | 45 | virtual MStatus compute(const MPlug &plug, MDataBlock &dataBlock); 46 | 47 | static MStatus setValue(MFnDependencyNode &fnNode, const char* attributeName, int &value); 48 | static MStatus getValue(MFnDependencyNode &fnNode, const char* attributeName, int &value); 49 | 50 | static MStatus setValues(MFnDependencyNode &fnNode, const char* attributeName, vector &values); 51 | static MStatus getValues(MFnDependencyNode &fnNode, const char* attributeName, vector &values); 52 | 53 | static MStatus onInitializePlugin(); 54 | static MStatus onUninitializePlugin(); 55 | 56 | static MStatus getCacheKey(MObject &node, string &key); 57 | static MStatus getCacheKeyFromMesh(MDagPath &node, string &key); 58 | 59 | public: 60 | static MObject numberOfEdges; 61 | static MObject numberOfFaces; 62 | static MObject numberOfVertices; 63 | 64 | static MObject edgeSymmetry; 65 | static MObject faceSymmetry; 66 | static MObject vertexSymmetry; 67 | 68 | static MObject edgeSides; 69 | static MObject faceSides; 70 | static MObject vertexSides; 71 | 72 | static MObject vertexChecksum; 73 | 74 | static MString NODE_NAME; 75 | static MTypeId NODE_ID; 76 | }; 77 | 78 | #endif -------------------------------------------------------------------------------- /src/polySymmetryCmd.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef POLY_SYMMETRY_CMD_H 7 | #define POLY_SYMMETRY_CMD_H 8 | 9 | #include "meshData.h" 10 | #include "polySymmetry.h" 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | using namespace std; 23 | 24 | #define SYMMETRY_COMPONENTS_FLAG "-sym" 25 | #define SYMMETRY_COMPONENTS_LONG_FLAG "-symmetry" 26 | 27 | #define LEFT_SIDE_VERTEX_FLAG "-lsv" 28 | #define LEFT_SIDE_VERTEX_LONG_FLAG "-leftSideVertex" 29 | 30 | #define CONSTRUCTION_HISTORY_FLAG "-ch" 31 | #define CONSTRUCTION_HISTORY_LONG_FLAG "-constructionHistory" 32 | 33 | #define EXISTS_FLAG "-ex" 34 | #define EXISTS_LONG_FLAG "-exists" 35 | 36 | 37 | class PolySymmetryCommand : public MPxToolCommand 38 | { 39 | public: 40 | PolySymmetryCommand(); 41 | virtual ~PolySymmetryCommand(); 42 | 43 | static void* creator(); 44 | static MSyntax getSyntax(); 45 | 46 | virtual MStatus doIt(const MArgList& argList); 47 | virtual MStatus redoIt(); 48 | virtual MStatus undoIt(); 49 | 50 | virtual MStatus doQueryDataAction(); 51 | virtual MStatus doQueryMeshAction(); 52 | virtual MStatus doUndoableCommand(); 53 | 54 | virtual MStatus parseQueryArguments(MArgDatabase &argsData); 55 | virtual MStatus parseArguments(MArgDatabase &argsData); 56 | 57 | virtual MStatus getSelectedMesh(MArgDatabase &argsData); 58 | 59 | virtual MStatus getSymmetryComponents(MArgDatabase &argsData); 60 | virtual void setSymmetryComponents(vector &components); 61 | 62 | virtual MStatus getLeftSideVertexIndices(MArgDatabase &argsData); 63 | virtual void setLeftSideVertexIndices(vector &indices); 64 | 65 | virtual MStatus getFlagStringArguments(MArgList &args, MSelectionList &selection); 66 | 67 | virtual MStatus getSymmetricalComponentsFromNode(); 68 | virtual MStatus getSymmetricalComponentsFromScene(); 69 | 70 | virtual MStatus createResultNode(); 71 | virtual MStatus createResultString(); 72 | 73 | virtual MStatus finalize(); 74 | 75 | const bool isUndoable(); 76 | const bool hasSyntax() { return true; } 77 | 78 | static void setJSONData(const char* key, stringstream &output, vector &data, bool isLast=false); 79 | 80 | public: 81 | static MString COMMAND_NAME; 82 | 83 | private: 84 | bool constructionHistory = false; 85 | bool isQuery = false; 86 | bool isQueryExists = false; 87 | 88 | MDagPath selectedMesh; 89 | MeshData meshData; 90 | PolySymmetryData meshSymmetryData; 91 | 92 | MObject meshSymmetryNode; 93 | 94 | vector symmetryComponents; 95 | vector leftSideVertexIndices; 96 | }; 97 | 98 | #endif -------------------------------------------------------------------------------- /polySymmetry/scripts/polySymmetry/__init__.py: -------------------------------------------------------------------------------- 1 | """polySymmetry 2 | 3 | The _initializePlugin and _uninitializePlugin functions are automatically 4 | called when the plugin is un/loaded to create the polySymmetry tools menu. 5 | 6 | """ 7 | 8 | import polySymmetry.commands as _cmds 9 | import polySymmetry.options as _opts 10 | import polySymmetry.tools as _tools 11 | 12 | import maya.api.OpenMaya as OpenMaya 13 | import maya.cmds as cmds 14 | 15 | 16 | _POLY_SYMMETRY_MENU_NAME = 'polySymmetryMenu' 17 | 18 | 19 | class _MenuItem(object): 20 | """Simple wrapper to keep the script editor is pretty. 21 | 22 | Attributes 23 | ---------- 24 | name : str 25 | Label for this menu item. 26 | action : callable 27 | Command executed when this menu item is clicked. 28 | isOptionBox : bool 29 | If True, this menu item is an option box. 30 | 31 | """ 32 | 33 | def __init__(self, name, action=None, isOptionBox=False): 34 | self.name = name 35 | self.action = action 36 | self.isOptionBox = isOptionBox 37 | 38 | def __str__(self): 39 | return self.name.replace(' ', '') 40 | 41 | def __call__(self, *args, **kwargs): 42 | try: 43 | self.action() 44 | except Exception as e: 45 | OpenMaya.MGlobal.displayError(str(e).strip()) 46 | 47 | 48 | _POLY_SYMMETRY_MENU_ITEMS = ( 49 | _MenuItem('Poly Symmetry Tool', _tools.polySymmetryTool), 50 | None, 51 | _MenuItem('Flip Mesh', _cmds.flipMesh), 52 | _MenuItem('Mirror Mesh', _cmds.mirrorMesh), 53 | None, 54 | _MenuItem("Copy Poly Deformer Weights", _cmds.copyPolyDeformerWeights), 55 | _MenuItem("Copy Poly Deformer Weights Options", _opts.copyPolyDeformerWeightsOptions, True), 56 | _MenuItem("Flip Poly Deformer Weights", _cmds.flipDeformerWeights), 57 | _MenuItem("Flip Poly Deformer Weights Options", _opts.flipPolyDeformerWeightsOptions, True), 58 | _MenuItem("Mirror Poly Deformer Weights", _cmds.mirrorDeformerWeights), 59 | _MenuItem("Mirror Poly Deformer Weights Options", _opts.mirrorPolyDeformerWeightsOptions, True), 60 | None, 61 | _MenuItem("Copy Poly Skin Weights", _cmds.copyPolySkinWeights), 62 | _MenuItem("Copy Poly Skin Weights Options", _opts.copyPolySkinWeightsOptions, True), 63 | _MenuItem("Mirror Poly Skin Weights", _cmds.mirrorPolySkinWeights), 64 | _MenuItem("Mirror Poly Skin Weights Options", _opts.mirrorPolySkinWeightsOptions, True), 65 | None, 66 | _MenuItem("Set Influence Symmetry", _cmds.setInfluenceSymmetry), 67 | _MenuItem("Set Influence Symmetry Options", _opts.InfluenceSymmetryOptionsBox, True), 68 | _MenuItem("Print Influence Symmetry", _cmds.printInfluenceSymmetry), 69 | None 70 | ) 71 | 72 | 73 | def _try_delete_menu(): 74 | if cmds.menu(_POLY_SYMMETRY_MENU_NAME, query=True, exists=True): 75 | cmds.deleteUI(_POLY_SYMMETRY_MENU_NAME) 76 | 77 | 78 | def _initializePlugin(*args): 79 | """Construct the poly symmetry plugin menu.""" 80 | 81 | if cmds.about(batch=True): 82 | return 83 | 84 | _try_delete_menu() 85 | 86 | cmds.menu( 87 | _POLY_SYMMETRY_MENU_NAME, 88 | label="Poly Symmetry", 89 | parent='MayaWindow' 90 | ) 91 | 92 | for item in _POLY_SYMMETRY_MENU_ITEMS: 93 | _addMenuItem(item) 94 | 95 | _addMenuItem( 96 | _MenuItem('Reload Menu', _initializePlugin) 97 | ) 98 | 99 | cmds.menuSet("riggingMenuSet", addMenu=_POLY_SYMMETRY_MENU_NAME) 100 | 101 | 102 | def _addMenuItem(item): 103 | if item is None: 104 | cmds.menuItem(divider=True) 105 | else: 106 | cmds.menuItem( 107 | label=item.name, 108 | command=item, 109 | sourceType='python', 110 | optionBox=item.isOptionBox 111 | ) 112 | 113 | 114 | def _uninitializePlugin(): 115 | """Construct the poly symmetry plugin menu.""" 116 | 117 | if cmds.about(batch=True): 118 | return 119 | 120 | _try_delete_menu() -------------------------------------------------------------------------------- /src/pluginMain.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | */ 4 | 5 | #include "polyChecksumCommand.h" 6 | #include "polyDeformerWeights.h" 7 | #include "polyFlipCmd.h" 8 | #include "polyMirrorCmd.h" 9 | #include "polySkinWeights.h" 10 | #include "polySymmetryTool.h" 11 | #include "polySymmetryCmd.h" 12 | #include "polySymmetryNode.h" 13 | #include "sceneCache.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | const char* kAUTHOR = "Ryan Porter"; 22 | const char* kVERSION = "0.6.0"; 23 | const char* kREQUIRED_API_VERSION = "Any"; 24 | 25 | MString PolyChecksumCommand::COMMAND_NAME = "polyChecksum"; 26 | MString PolyDeformerWeightsCommand::COMMAND_NAME = "polyDeformerWeights"; 27 | MString PolyFlipCommand::COMMAND_NAME = "polyFlip"; 28 | MString PolyMirrorCommand::COMMAND_NAME = "polyMirror"; 29 | MString PolySkinWeightsCommand::COMMAND_NAME = "polySkinWeights"; 30 | 31 | MString PolySymmetryContextCmd::COMMAND_NAME = "polySymmetryCtx"; 32 | MString PolySymmetryCommand::COMMAND_NAME = "polySymmetry"; 33 | 34 | MString PolySymmetryNode::NODE_NAME = "polySymmetryData"; 35 | MTypeId PolySymmetryNode::NODE_ID = 0x00126b0d; 36 | 37 | #define REGISTER_COMMAND(CMD) CHECK_MSTATUS_AND_RETURN_IT(fnPlugin.registerCommand(CMD::COMMAND_NAME, CMD::creator, CMD::getSyntax)); 38 | #define DEREGISTER_COMMAND(CMD) CHECK_MSTATUS_AND_RETURN_IT(fnPlugin.deregisterCommand(CMD::COMMAND_NAME)) 39 | 40 | bool menuCreated = false; 41 | 42 | MStatus initializePlugin(MObject obj) 43 | { 44 | MStatus status; 45 | MFnPlugin fnPlugin(obj, kAUTHOR, kVERSION, kREQUIRED_API_VERSION); 46 | 47 | status = fnPlugin.registerContextCommand( 48 | PolySymmetryContextCmd::COMMAND_NAME, 49 | PolySymmetryContextCmd::creator, 50 | PolySymmetryCommand::COMMAND_NAME, 51 | PolySymmetryCommand::creator, 52 | PolySymmetryCommand::getSyntax 53 | ); 54 | 55 | CHECK_MSTATUS_AND_RETURN_IT(status); 56 | 57 | status = fnPlugin.registerNode( 58 | PolySymmetryNode::NODE_NAME, 59 | PolySymmetryNode::NODE_ID, 60 | PolySymmetryNode::creator, 61 | PolySymmetryNode::initialize, 62 | MPxNode::kDependNode 63 | ); 64 | 65 | CHECK_MSTATUS_AND_RETURN_IT(status); 66 | 67 | REGISTER_COMMAND(PolyChecksumCommand); 68 | REGISTER_COMMAND(PolyDeformerWeightsCommand); 69 | REGISTER_COMMAND(PolyFlipCommand); 70 | REGISTER_COMMAND(PolyMirrorCommand); 71 | REGISTER_COMMAND(PolySkinWeightsCommand); 72 | 73 | status = PolySymmetryCache::initialize(); 74 | CHECK_MSTATUS_AND_RETURN_IT(status); 75 | 76 | if (MGlobal::mayaState() == MGlobal::kInteractive) 77 | { 78 | status = MGlobal::executePythonCommand("import polySymmetry"); 79 | 80 | if (status) 81 | { 82 | MGlobal::executePythonCommand("polySymmetry._initializePlugin()"); 83 | menuCreated = true; 84 | } else { 85 | MGlobal::displayWarning("polySymmetry module has not been installed - cannot create a tools menu."); 86 | } 87 | } 88 | 89 | return MS::kSuccess; 90 | } 91 | 92 | 93 | MStatus uninitializePlugin(MObject obj) 94 | { 95 | MStatus status; 96 | MFnPlugin fnPlugin(obj, kAUTHOR, kVERSION, kREQUIRED_API_VERSION); 97 | 98 | status = PolySymmetryCache::uninitialize(); 99 | CHECK_MSTATUS_AND_RETURN_IT(status); 100 | 101 | status = fnPlugin.deregisterContextCommand( 102 | PolySymmetryContextCmd::COMMAND_NAME, 103 | PolySymmetryCommand::COMMAND_NAME 104 | ); 105 | 106 | CHECK_MSTATUS_AND_RETURN_IT(status); 107 | 108 | DEREGISTER_COMMAND(PolyChecksumCommand); 109 | DEREGISTER_COMMAND(PolyDeformerWeightsCommand); 110 | DEREGISTER_COMMAND(PolyFlipCommand); 111 | DEREGISTER_COMMAND(PolyMirrorCommand); 112 | DEREGISTER_COMMAND(PolySkinWeightsCommand); 113 | 114 | status = fnPlugin.deregisterNode(PolySymmetryNode::NODE_ID); 115 | CHECK_MSTATUS_AND_RETURN_IT(status); 116 | 117 | if (MGlobal::mayaState() == MGlobal::kInteractive && menuCreated) 118 | { 119 | status = MGlobal::executePythonCommand("import polySymmetry"); 120 | 121 | if (status) 122 | { 123 | MGlobal::executePythonCommand("polySymmetry._uninitializePlugin()"); 124 | } 125 | } 126 | 127 | return MS::kSuccess; 128 | } -------------------------------------------------------------------------------- /src/polyMirrorCmd.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include 7 | 8 | #include "polyMirrorCmd.h" 9 | #include "polySymmetryNode.h" 10 | #include "sceneCache.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | using namespace std; 29 | 30 | PolyMirrorCommand::PolyMirrorCommand() {} 31 | PolyMirrorCommand::~PolyMirrorCommand() {} 32 | 33 | void* PolyMirrorCommand::creator() 34 | { 35 | return new PolyMirrorCommand(); 36 | } 37 | 38 | MSyntax PolyMirrorCommand::getSyntax() 39 | { 40 | MSyntax syntax; 41 | 42 | syntax.setObjectType(MSyntax::kSelectionList, 2, 2); 43 | syntax.useSelectionAsDefault(true); 44 | 45 | syntax.enableQuery(false); 46 | syntax.enableEdit(false); 47 | 48 | return syntax; 49 | } 50 | 51 | MStatus PolyMirrorCommand::doIt(const MArgList& argList) 52 | { 53 | MStatus status; 54 | 55 | MArgDatabase argsData(syntax(), argList); 56 | 57 | MSelectionList selection; 58 | argsData.getObjects(selection); 59 | 60 | MStatus baseMeshStatus = selection.getDagPath(0, this->baseMesh); 61 | 62 | MStatus targetMeshStatus = selection.getDagPath(1, this->targetMesh); 63 | 64 | if (!this->baseMesh.hasFn(MFn::kMesh) || !this->targetMesh.hasFn(MFn::kMesh)) 65 | { 66 | MGlobal::displayError("polyMirror command requires a a base mesh and a target mesh."); 67 | return MStatus::kFailure; 68 | } 69 | 70 | MFnMesh fnBaseMesh(this->baseMesh); 71 | MFnMesh fnTargetMesh(this->targetMesh); 72 | 73 | if ( 74 | (fnBaseMesh.numVertices() != fnTargetMesh.numVertices()) 75 | || (fnBaseMesh.numEdges() != fnTargetMesh.numEdges()) 76 | || (fnBaseMesh.numPolygons() != fnTargetMesh.numPolygons()) 77 | ) { 78 | MString errorMsg("Base mesh and target mesh are not point compatible."); 79 | MGlobal::displayError(errorMsg); 80 | 81 | return MStatus::kFailure; 82 | } 83 | 84 | bool cacheHit = PolySymmetryCache::getNodeFromCache(this->targetMesh, this->polySymmetryData); 85 | 86 | if (!cacheHit) 87 | { 88 | MString errorMsg("^1s has not had it's symmetry computed."); 89 | errorMsg.format(errorMsg, this->targetMesh.partialPathName()); 90 | 91 | MGlobal::displayError(errorMsg); 92 | 93 | return MStatus::kFailure; 94 | } 95 | 96 | return this->redoIt(); 97 | } 98 | 99 | MStatus PolyMirrorCommand::redoIt() 100 | { 101 | vector vertexSymmetry; 102 | vector vertexSides; 103 | 104 | MFnDependencyNode fnNode(this->polySymmetryData); 105 | 106 | PolySymmetryNode::getValues(fnNode, VERTEX_SYMMETRY, vertexSymmetry); 107 | PolySymmetryNode::getValues(fnNode, VERTEX_SIDES, vertexSides); 108 | 109 | MPointArray basePoints; 110 | 111 | MItGeometry itBaseGeo(this->baseMesh); 112 | MItGeometry itTargetGeo(this->targetMesh); 113 | 114 | itBaseGeo.allPositions(basePoints, MSpace::kObject); 115 | itTargetGeo.allPositions(originalPoints, MSpace::kObject); 116 | 117 | int numberOfVertices = -1; 118 | PolySymmetryNode::getValue(fnNode, NUMBER_OF_VERTICES, numberOfVertices); 119 | MPointArray newPoints(numberOfVertices); 120 | 121 | MPoint basePnt; 122 | MPoint origPnt; 123 | MPoint newPnt; 124 | 125 | for (int i = 0; i < numberOfVertices; i++) 126 | { 127 | int o = vertexSymmetry[i]; 128 | newPnt = originalPoints[o]; 129 | 130 | newPoints.set(i, newPnt.x * -1.0, newPnt.y, newPnt.z); 131 | } 132 | 133 | for (int i = 0; i < numberOfVertices; i++) 134 | { 135 | origPnt = originalPoints[i]; 136 | basePnt = basePoints[i]; 137 | newPnt = newPoints[i]; 138 | 139 | newPoints.set( 140 | i, 141 | origPnt.x + newPnt.x - basePnt.x, 142 | origPnt.y + newPnt.y - basePnt.y, 143 | origPnt.z + newPnt.z - basePnt.z 144 | ); 145 | } 146 | 147 | itTargetGeo.setAllPositions(newPoints, MSpace::kObject); 148 | 149 | return MStatus::kSuccess; 150 | } 151 | 152 | MStatus PolyMirrorCommand::undoIt() 153 | { 154 | MFnMesh fnMesh(this->targetMesh); 155 | fnMesh.setPoints(originalPoints, MSpace::kObject); 156 | 157 | return MStatus::kSuccess; 158 | } 159 | -------------------------------------------------------------------------------- /src/polySkinWeights.h: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #ifndef POLY_SKIN_WEIGHTS_H 7 | #define POLY_SKIN_WEIGHTS_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | using namespace std; 29 | 30 | struct JointLabel 31 | { 32 | int side = -1; 33 | int type = -1; 34 | MString otherType; 35 | 36 | JointLabel() {} 37 | }; 38 | 39 | class PolySkinWeightsCommand : public MPxCommand 40 | { 41 | public: 42 | PolySkinWeightsCommand(); 43 | virtual ~PolySkinWeightsCommand(); 44 | 45 | static void* creator(); 46 | static MSyntax getSyntax(); 47 | 48 | virtual MStatus parseArguments(MArgDatabase &argsData); 49 | virtual MStatus parseEditArguments(MArgDatabase &argsData); 50 | virtual MStatus parseQueryArguments(MArgDatabase &argsData); 51 | virtual MStatus parseInfluenceSymmetryArgument(MArgDatabase &argsData); 52 | 53 | virtual MStatus validateArguments(); 54 | virtual MStatus validateEditArguments(); 55 | virtual MStatus validateQueryArguments(); 56 | virtual MStatus validateInfluenceSymmetryArgument(); 57 | 58 | virtual bool isDeformedBy(MObject &skin, MDagPath &mesh); 59 | 60 | virtual MStatus doIt(const MArgList& argList); 61 | virtual MStatus redoIt(); 62 | virtual MStatus undoIt(); 63 | 64 | virtual MStatus copyPolySkinWeights(); 65 | virtual MStatus editPolySkinWeights(); 66 | virtual MStatus queryPolySkinWeights(); 67 | 68 | virtual MStatus undoCopyPolySkinWeights(); 69 | virtual MStatus undoEditPolySkinWeights(); 70 | 71 | virtual void copyWeightsTable(vector &influenceKeys); 72 | virtual void flipWeightsTable(vector &influenceKeys); 73 | virtual void mirrorWeightsTable(vector &influenceKeys); 74 | 75 | virtual MStatus makeInfluencesMatch(MFnSkinCluster &fnSourceSkin, MFnSkinCluster &fnDestinationSkin); 76 | virtual MStatus makeInfluenceSymmetryTable(MDagPathArray &influences, vector &influenceKeys, unordered_map &jointLabels); 77 | 78 | virtual void setupWeightTables(vector &influenceKeys); 79 | virtual void setWeightsTable(unordered_map> &weightTable, MDoubleArray &weights, vector &influenceKeys); 80 | virtual void getWeightsTable(unordered_map> &weightTable, MDoubleArray &weights, vector &influenceKeys); 81 | 82 | virtual MStatus getInfluenceIndices(MFnSkinCluster &fnSkin, MIntArray &influenceIndices); 83 | virtual MStatus getInfluenceKeys(MFnSkinCluster &fnSkin, vector &influenceKeys); 84 | 85 | virtual void getJointLabels(MDagPathArray &influences, vector &influenceKeys, unordered_map &jointLabels); 86 | virtual JointLabel getJointLabel(MDagPath &influence); 87 | virtual MStatus setJointLabel(MDagPath &influence, JointLabel &jointLabel); 88 | 89 | virtual bool isUndoable() const; 90 | virtual bool hasSyntax() const { return true; } 91 | 92 | public: 93 | static MString COMMAND_NAME; 94 | 95 | private: 96 | int direction = 1; 97 | 98 | bool normalizeWeights = false; 99 | bool mirrorWeights = false; 100 | bool flipWeights = false; 101 | 102 | bool isQuery = false; 103 | bool isEdit = false; 104 | 105 | bool isInfluenceSymmetryFlagSet = false; 106 | bool isQueryInfluenceSymmetry = false; 107 | 108 | uint numberOfVertices; 109 | 110 | string leftInfluencePattern; 111 | string rightInfluencePattern; 112 | 113 | unordered_map influenceSymmetry; 114 | unordered_map oldJointLabels; 115 | unordered_map> oldWeights; 116 | unordered_map> newWeights; 117 | 118 | vector selectedVertexIndices; 119 | 120 | MDGModifier dgModifier; 121 | MDoubleArray oldWeightValues; 122 | 123 | MObject sourceComponents; 124 | MDagPath sourceMesh; 125 | MObject sourceSkin; 126 | 127 | MObject polySymmetryData; 128 | 129 | MObject destinationComponents; 130 | MDagPath destinationMesh; 131 | MObject destinationSkin; 132 | }; 133 | 134 | #endif -------------------------------------------------------------------------------- /src/sceneCache.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "polySymmetryNode.h" 11 | #include "sceneCache.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | 24 | unordered_map PolySymmetryCache::symmetryNodeCache; 25 | MCallbackIdArray PolySymmetryCache::callbackIDs; 26 | bool PolySymmetryCache::cacheNodes; 27 | 28 | MStatus PolySymmetryCache::initialize() 29 | { 30 | MStatus status; 31 | 32 | PolySymmetryCache::cacheNodes = true; 33 | PolySymmetryCache::symmetryNodeCache = unordered_map(); 34 | PolySymmetryCache::callbackIDs = MCallbackIdArray(); 35 | 36 | MCallbackId sceneUpdateCallbackId = MSceneMessage::addCallback(MSceneMessage::kSceneUpdate, PolySymmetryCache::sceneUpdateCallback, &status); 37 | CHECK_MSTATUS_AND_RETURN_IT(status); 38 | 39 | MCallbackId afterNewCallbackId = MSceneMessage::addCallback(MSceneMessage::kAfterNew, PolySymmetryCache::newFileCallback, &status); 40 | CHECK_MSTATUS_AND_RETURN_IT(status); 41 | 42 | MCallbackId beforeOpenCallbackId = MSceneMessage::addCallback(MSceneMessage::kBeforeOpen, PolySymmetryCache::beforeOpenFileCallback, &status); 43 | CHECK_MSTATUS_AND_RETURN_IT(status); 44 | 45 | MCallbackId afterOpenCallbackId = MSceneMessage::addCallback(MSceneMessage::kAfterOpen, PolySymmetryCache::afterOpenFileCallback, &status); 46 | CHECK_MSTATUS_AND_RETURN_IT(status); 47 | 48 | MCallbackId nodeAddedCallbackId = MDGMessage::addNodeAddedCallback(PolySymmetryCache::nodeAddedCallback, PolySymmetryNode::NODE_NAME, NULL, &status); 49 | CHECK_MSTATUS_AND_RETURN_IT(status); 50 | 51 | MCallbackId nodeRemovedCallbackId = MDGMessage::addNodeRemovedCallback(PolySymmetryCache::nodeRemovedCallback, PolySymmetryNode::NODE_NAME, NULL, &status); 52 | CHECK_MSTATUS_AND_RETURN_IT(status); 53 | 54 | callbackIDs.append(sceneUpdateCallbackId); 55 | callbackIDs.append(afterNewCallbackId); 56 | callbackIDs.append(beforeOpenCallbackId); 57 | callbackIDs.append(afterOpenCallbackId); 58 | callbackIDs.append(nodeAddedCallbackId); 59 | callbackIDs.append(nodeRemovedCallbackId); 60 | 61 | return MStatus::kSuccess; 62 | } 63 | 64 | MStatus PolySymmetryCache::uninitialize() 65 | { 66 | MStatus status; 67 | 68 | status = MMessage::removeCallbacks(callbackIDs); 69 | CHECK_MSTATUS_AND_RETURN_IT(status); 70 | 71 | return MStatus::kSuccess; 72 | } 73 | 74 | void PolySymmetryCache::sceneUpdateCallback(void* clientData) 75 | { 76 | if (PolySymmetryCache::cacheNodes) 77 | { 78 | PolySymmetryCache::cacheNodes = false; 79 | } 80 | } 81 | 82 | void PolySymmetryCache::nodeAddedCallback(MObject &node, void* clientData) 83 | { 84 | if (PolySymmetryCache::cacheNodes) 85 | { 86 | PolySymmetryCache::addNodeToCache(node); 87 | } 88 | } 89 | 90 | void PolySymmetryCache::nodeRemovedCallback(MObject &node, void* clientData) 91 | { 92 | if (!PolySymmetryCache::cacheNodes) { return; } 93 | 94 | string key; 95 | PolySymmetryNode::getCacheKey(node, key); 96 | 97 | if (!key.empty() && PolySymmetryCache::symmetryNodeCache.count(key) > 0) 98 | { 99 | PolySymmetryCache::symmetryNodeCache.erase(key); 100 | } 101 | } 102 | 103 | void PolySymmetryCache::newFileCallback(void* clientData) 104 | { 105 | PolySymmetryCache::symmetryNodeCache.clear(); 106 | } 107 | 108 | void PolySymmetryCache::beforeOpenFileCallback(void* clientData) 109 | { 110 | PolySymmetryCache::cacheNodes = false; 111 | } 112 | 113 | void PolySymmetryCache::afterOpenFileCallback(void* clientData) 114 | { 115 | PolySymmetryCache::cacheNodes = true; 116 | 117 | PolySymmetryCache::symmetryNodeCache.clear(); 118 | 119 | MItDependencyNodes itNodes; 120 | MFnDependencyNode fnNode; 121 | MObject node; 122 | 123 | while (!itNodes.isDone()) 124 | { 125 | node = itNodes.thisNode(); 126 | 127 | fnNode.setObject(node); 128 | 129 | if (fnNode.typeName() == PolySymmetryNode::NODE_NAME) 130 | { 131 | PolySymmetryCache::addNodeToCache(node); 132 | } 133 | 134 | itNodes.next(); 135 | } 136 | } 137 | 138 | void PolySymmetryCache::addNodeToCache(MObject &node) 139 | { 140 | string key; 141 | PolySymmetryNode::getCacheKey(node, key); 142 | 143 | if (!key.empty()) 144 | { 145 | MObjectHandle handle(node); 146 | PolySymmetryCache::symmetryNodeCache.emplace(key, handle); 147 | } 148 | } 149 | 150 | bool PolySymmetryCache::getNodeFromCache(MDagPath &mesh, MObject &node) 151 | { 152 | bool result = false; 153 | 154 | string key; 155 | PolySymmetryNode::getCacheKeyFromMesh(mesh, key); 156 | 157 | if (!key.empty()) 158 | { 159 | auto got = PolySymmetryCache::symmetryNodeCache.find(key); 160 | result = got != PolySymmetryCache::symmetryNodeCache.end(); 161 | 162 | if (result) 163 | { 164 | node = (*got).second.object(); 165 | } 166 | } 167 | 168 | return result; 169 | } -------------------------------------------------------------------------------- /src/meshData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include "polyChecksum.h" 7 | #include "meshData.h" 8 | #include "util.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | 22 | MeshData::MeshData() {} 23 | 24 | MeshData::~MeshData() 25 | { 26 | this->clear(); 27 | } 28 | 29 | void MeshData::clear() 30 | { 31 | numberOfEdges = 0; 32 | numberOfFaces = 0; 33 | numberOfVertices = 0; 34 | 35 | vertexData.clear(); 36 | edgeData.clear(); 37 | faceData.clear(); 38 | } 39 | 40 | unsigned long MeshData::getVertexChecksum(MDagPath &meshDagPath) 41 | { 42 | PolyChecksum checksum; 43 | 44 | MItMeshVertex itVertex(meshDagPath); 45 | 46 | while (!itVertex.isDone()) 47 | { 48 | int index = itVertex.index(); 49 | checksum.putBytes(&index, sizeof(index)); 50 | 51 | MIntArray connectedVertices; 52 | itVertex.getConnectedVertices(connectedVertices); 53 | uint numConnectedVertices = connectedVertices.length(); 54 | 55 | for (uint i = 0; i < numConnectedVertices; i++) 56 | { 57 | uint idx = connectedVertices[i]; 58 | checksum.putBytes(&idx, sizeof(idx)); 59 | } 60 | 61 | itVertex.next(); 62 | } 63 | 64 | return checksum.getResult(); 65 | } 66 | 67 | void MeshData::unpackMesh(MDagPath &meshDagPath) 68 | { 69 | this->clear(); 70 | 71 | MItMeshEdge edges(meshDagPath); 72 | MItMeshPolygon faces(meshDagPath); 73 | MItMeshVertex vertices(meshDagPath); 74 | 75 | this->unpackEdges(edges); 76 | this->unpackFaces(faces); 77 | this->unpackVertices(vertices); 78 | 79 | this->unpackVertexSiblings(); 80 | } 81 | 82 | void MeshData::unpackEdges(MItMeshEdge &edges) 83 | { 84 | this->numberOfEdges = edges.count(); 85 | this->edgeData.resize(this->numberOfEdges); 86 | 87 | MIntArray connectedEdges; 88 | MIntArray connectedFaces; 89 | 90 | edges.reset(); 91 | 92 | while (!edges.isDone()) 93 | { 94 | EdgeData &edge = edgeData[edges.index()]; 95 | 96 | edge.connectedVertices.resize(2); 97 | edge.connectedVertices[0] = edges.index(0); 98 | edge.connectedVertices[1] = edges.index(1); 99 | 100 | sort(edge.connectedVertices.begin(), edge.connectedVertices.end()); 101 | 102 | edges.getConnectedFaces(connectedFaces); 103 | edges.getConnectedEdges(connectedEdges); 104 | 105 | insertAll(connectedFaces, edge.connectedFaces); 106 | insertAll(connectedEdges, edge.connectedEdges); 107 | 108 | edges.next(); 109 | } 110 | } 111 | 112 | void MeshData::unpackFaces(MItMeshPolygon &faces) 113 | { 114 | this->numberOfFaces = faces.count(); 115 | this->faceData.resize(this->numberOfFaces); 116 | 117 | MIntArray connectedEdges; 118 | MIntArray connectedFaces; 119 | MIntArray connectedVertices; 120 | 121 | faces.reset(); 122 | 123 | while (!faces.isDone()) 124 | { 125 | FaceData &face = faceData[faces.index()]; 126 | 127 | faces.getEdges(connectedEdges); 128 | faces.getConnectedFaces(connectedFaces); 129 | faces.getVertices(connectedVertices); 130 | 131 | insertAll(connectedFaces, face.connectedFaces); 132 | insertAll(connectedEdges, face.connectedEdges); 133 | insertAll(connectedVertices, face.connectedVertices); 134 | 135 | faces.next(); 136 | } 137 | } 138 | 139 | void MeshData::unpackVertices(MItMeshVertex &vertices) 140 | { 141 | this->numberOfVertices = vertices.count(); 142 | this->vertexData.resize(this->numberOfVertices); 143 | 144 | MIntArray connectedEdges; 145 | MIntArray connectedFaces; 146 | MIntArray connectedVertices; 147 | 148 | vertices.reset(); 149 | 150 | while (!vertices.isDone()) 151 | { 152 | VertexData &vertex = vertexData[vertices.index()]; 153 | 154 | vertices.getConnectedEdges(connectedEdges); 155 | vertices.getConnectedFaces(connectedFaces); 156 | vertices.getConnectedVertices(connectedVertices); 157 | 158 | insertAll(connectedFaces, vertex.connectedFaces); 159 | insertAll(connectedEdges, vertex.connectedEdges); 160 | insertAll(connectedVertices, vertex.connectedVertices); 161 | 162 | vertices.next(); 163 | } 164 | } 165 | 166 | void MeshData::unpackVertexSiblings() 167 | { 168 | for (int vertexIndex = 0; vertexIndex < this->numberOfVertices; vertexIndex++) 169 | { 170 | for (int &faceIndex : vertexData[vertexIndex].connectedFaces) 171 | { 172 | vertexData[vertexIndex].faceVertexSiblings.emplace(faceIndex, vector()); 173 | 174 | for (int &faceVertexIndex : faceData[faceIndex].connectedVertices) 175 | { 176 | if (contains(vertexData[faceVertexIndex].connectedVertices, vertexIndex)) 177 | { 178 | vertexData[vertexIndex].faceVertexSiblings[faceIndex].push_back(faceVertexIndex); 179 | } 180 | } 181 | 182 | sort( 183 | vertexData[vertexIndex].faceVertexSiblings[faceIndex].begin(), 184 | vertexData[vertexIndex].faceVertexSiblings[faceIndex].end() 185 | ); 186 | } 187 | } 188 | } 189 | 190 | void MeshData::insertAll(MIntArray &src, vector &dest) 191 | { 192 | dest.resize(src.length()); 193 | 194 | for (uint i = 0; i < src.length(); i++) 195 | { 196 | dest[i] = src[i]; 197 | } 198 | 199 | sort(dest.begin(), dest.end()); 200 | } -------------------------------------------------------------------------------- /src/polyFlipCmd.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include 7 | 8 | #include "polyFlipCmd.h" 9 | #include "polySymmetryNode.h" 10 | #include "sceneCache.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | using namespace std; 29 | 30 | #define WORLD_SPACE_FLAG "-ws" 31 | #define WORLD_SPACE_LONG_FLAG "-worldSpace" 32 | 33 | #define OBJECT_SPACE_FLAG "-os" 34 | #define OBJECT_SPACE_LONG_FLAG "-objectSpace" 35 | 36 | #define REFERENCE_MESH_FLAG "-ref" 37 | #define REFERENCE_MESH_LONG_FLAG "-referenceFlag" 38 | 39 | PolyFlipCommand::PolyFlipCommand() {} 40 | PolyFlipCommand::~PolyFlipCommand() {} 41 | 42 | void* PolyFlipCommand::creator() 43 | { 44 | return new PolyFlipCommand(); 45 | } 46 | 47 | MSyntax PolyFlipCommand::getSyntax() 48 | { 49 | MSyntax syntax; 50 | 51 | syntax.useSelectionAsDefault(true); 52 | syntax.setObjectType(MSyntax::kSelectionList, 1, 1); 53 | 54 | syntax.addFlag(WORLD_SPACE_FLAG, WORLD_SPACE_LONG_FLAG); 55 | syntax.addFlag(OBJECT_SPACE_FLAG, OBJECT_SPACE_LONG_FLAG); 56 | syntax.addFlag(REFERENCE_MESH_FLAG, REFERENCE_MESH_LONG_FLAG, MSyntax::kSelectionItem); 57 | 58 | syntax.enableQuery(false); 59 | syntax.enableEdit(false); 60 | 61 | return syntax; 62 | } 63 | 64 | MStatus PolyFlipCommand::doIt(const MArgList& argList) 65 | { 66 | MStatus status; 67 | 68 | MArgDatabase argsData(syntax(), argList); 69 | 70 | MSelectionList selection; 71 | argsData.getObjects(selection); 72 | 73 | status = selection.getDagPath(0, this->selectedMesh); 74 | 75 | if (!status || !this->selectedMesh.hasFn(MFn::kMesh)) 76 | { 77 | MGlobal::displayError("polyFlip command requires a mesh."); 78 | return MStatus::kFailure; 79 | } 80 | 81 | bool cacheHit = PolySymmetryCache::getNodeFromCache(this->selectedMesh, this->polySymmetryData); 82 | 83 | if (!cacheHit) 84 | { 85 | MString errorMsg("^1s has not had it's symmetry computed."); 86 | errorMsg.format(errorMsg, selectedMesh.partialPathName()); 87 | 88 | MGlobal::displayError(errorMsg); 89 | 90 | return MStatus::kFailure; 91 | } 92 | 93 | if (argsData.isFlagSet(WORLD_SPACE_FLAG)) 94 | { 95 | this->worldSpace = true; 96 | } 97 | 98 | if (argsData.isFlagSet(OBJECT_SPACE_FLAG)) 99 | { 100 | this->worldSpace = true; 101 | } 102 | 103 | if (argsData.isFlagSet(REFERENCE_MESH_FLAG)) 104 | { 105 | MSelectionList referenceMeshSelection; 106 | 107 | argsData.getObjects(referenceMeshSelection); 108 | 109 | status = referenceMeshSelection.getDagPath(0, this->referenceMesh); 110 | 111 | if (!status || !this->referenceMesh.hasFn(MFn::kMesh)) 112 | { 113 | MString errorMsg("%s flag requires a mesh."); 114 | errorMsg.format(errorMsg, MString(REFERENCE_MESH_LONG_FLAG)); 115 | 116 | MGlobal::displayError(errorMsg); 117 | return MStatus::kFailure; 118 | } 119 | 120 | MFnMesh fnRef(this->selectedMesh); 121 | MFnMesh fnSel(this->referenceMesh); 122 | 123 | if ( 124 | (fnRef.numVertices() != fnSel.numVertices()) 125 | || (fnRef.numEdges() != fnSel.numEdges()) 126 | || (fnRef.numPolygons() != fnSel.numPolygons()) 127 | ) { 128 | MString errorMsg("Reference mesh is not point compatible with selected mesh."); 129 | MGlobal::displayError(errorMsg); 130 | 131 | return MStatus::kFailure; 132 | } 133 | } 134 | 135 | return this->redoIt(); 136 | } 137 | 138 | MStatus PolyFlipCommand::redoIt() 139 | { 140 | if (this->worldSpace || this->objectSpace) 141 | { 142 | return this->flipMesh(); 143 | } else if (this->referenceMesh.isValid()) { 144 | return this->flipMeshAgainst(); 145 | } else { 146 | return MStatus::kSuccess; 147 | } 148 | } 149 | 150 | 151 | MStatus PolyFlipCommand::flipMesh() 152 | { 153 | MStatus status; 154 | 155 | MSpace::Space space = this->worldSpace ? MSpace::kWorld : MSpace::kObject; 156 | 157 | vector vertexSymmetry; 158 | vector vertexSides; 159 | 160 | MFnDependencyNode fnNode(this->polySymmetryData); 161 | 162 | PolySymmetryNode::getValues(fnNode, VERTEX_SYMMETRY, vertexSymmetry); 163 | PolySymmetryNode::getValues(fnNode, VERTEX_SIDES, vertexSides); 164 | 165 | MFnMesh fnMesh(this->selectedMesh); 166 | MItGeometry itGeo(this->selectedMesh); 167 | itGeo.allPositions(this->originalPoints, space); 168 | 169 | uint numberOfVertices = fnMesh.numVertices(); 170 | MPointArray newPoints(numberOfVertices); 171 | 172 | MPoint pnt; 173 | 174 | for (uint i = 0; i < numberOfVertices; i++) 175 | { 176 | int o = vertexSymmetry[i]; 177 | pnt = this->originalPoints[o]; 178 | 179 | newPoints.set(i, pnt.x * -1.0, pnt.y, pnt.z); 180 | } 181 | 182 | itGeo.setAllPositions(newPoints, space); 183 | 184 | return MStatus::kSuccess; 185 | } 186 | 187 | MStatus PolyFlipCommand::flipMeshAgainst() 188 | { 189 | MStatus status; 190 | 191 | MSpace::Space space = this->worldSpace ? MSpace::kWorld : MSpace::kObject; 192 | 193 | vector vertexSymmetry; 194 | vector vertexSides; 195 | 196 | MPointArray referencePoints; 197 | MFnDependencyNode fnNode(this->polySymmetryData); 198 | 199 | PolySymmetryNode::getValues(fnNode, VERTEX_SYMMETRY, vertexSymmetry); 200 | PolySymmetryNode::getValues(fnNode, VERTEX_SIDES, vertexSides); 201 | 202 | MFnMesh fnMesh(this->selectedMesh); 203 | 204 | MItGeometry itRef(this->referenceMesh); 205 | MItGeometry itGeo(this->selectedMesh); 206 | 207 | itRef.allPositions(referencePoints, space); 208 | itGeo.allPositions(this->originalPoints, space); 209 | 210 | uint numberOfVertices = fnMesh.numVertices(); 211 | MPointArray newPoints(numberOfVertices); 212 | 213 | MPoint pnt; 214 | MPoint ref; 215 | MVector delta; 216 | 217 | for (uint i = 0; i < numberOfVertices; i++) 218 | { 219 | if (vertexSides[i] == 0) 220 | { 221 | newPoints.set(this->originalPoints[i], i); 222 | } 223 | 224 | int o = vertexSymmetry[i]; 225 | pnt = this->originalPoints[o]; 226 | ref = referencePoints[o]; 227 | 228 | delta = pnt - ref; 229 | 230 | ref = referencePoints[i]; 231 | 232 | newPoints.set( 233 | i, 234 | (delta.x * -1.0) + ref.x, 235 | delta.y + ref.y, 236 | delta.z + ref.z 237 | ); 238 | } 239 | 240 | itGeo.setAllPositions(newPoints, space); 241 | 242 | return MStatus::kSuccess; 243 | } 244 | 245 | MStatus PolyFlipCommand::undoIt() 246 | { 247 | MFnMesh fnMesh(this->selectedMesh); 248 | fnMesh.setPoints(this->originalPoints, MSpace::kObject); 249 | 250 | return MStatus::kSuccess; 251 | } 252 | -------------------------------------------------------------------------------- /src/selection.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include "selection.h" 7 | #include "meshData.h" 8 | #include "util.h" 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | using namespace std; 25 | 26 | void getSelectedComponents( 27 | MDagPath &selectedMesh, 28 | MSelectionList &activeSelection, 29 | MSelectionList &selection, 30 | MFn::Type componentType) 31 | { 32 | MDagPath mesh; 33 | MObject component; 34 | 35 | MItSelectionList iterComponents(activeSelection, componentType); 36 | 37 | while (!iterComponents.isDone()) 38 | { 39 | iterComponents.getDagPath(mesh, component); 40 | 41 | if (mesh.node().hasFn(MFn::kMesh)) { mesh.pop(); } 42 | 43 | if (mesh == selectedMesh) 44 | { 45 | selection.add(mesh, component, true); 46 | } 47 | 48 | iterComponents.next(); 49 | } 50 | } 51 | 52 | 53 | void getSelectedComponentIndices( 54 | MSelectionList &activeSelection, 55 | vector &indices, 56 | MFn::Type componentType) 57 | { 58 | MDagPath mesh; 59 | MObject component; 60 | 61 | MItSelectionList iterComponents(activeSelection, componentType); 62 | 63 | while (!iterComponents.isDone()) 64 | { 65 | iterComponents.getDagPath(mesh, component); 66 | 67 | if (componentType == MFn::kMeshEdgeComponent) 68 | { 69 | MItMeshEdge iterGeo(mesh, component); 70 | 71 | while (!iterGeo.isDone()) 72 | { 73 | indices.push_back(iterGeo.index()); 74 | iterGeo.next(); 75 | } 76 | } else if (componentType == MFn::kMeshVertComponent) { 77 | MItMeshVertex iterGeo(mesh, component); 78 | 79 | while (!iterGeo.isDone()) 80 | { 81 | indices.push_back(iterGeo.index()); 82 | iterGeo.next(); 83 | } 84 | } else if (componentType == MFn::kMeshPolygonComponent) { 85 | MItMeshPolygon iterGeo(mesh, component); 86 | 87 | while (!iterGeo.isDone()) 88 | { 89 | indices.push_back(iterGeo.index()); 90 | iterGeo.next(); 91 | } 92 | } 93 | 94 | iterComponents.next(); 95 | } 96 | } 97 | 98 | /** 99 | * Returns true if the selection is a valid selection of symmetrical components and packs them into the ComponentSelection. 100 | * 101 | * A valid selection must contain: 102 | * - exactly two faces 103 | * - exactly two edges 104 | * - exactly two vertices if leftSideVertexSelected is false, otherwise exactly three faces 105 | * 106 | * Furthermore: 107 | * Each selected edge must be on exactly one of the selected faces. 108 | * Each selected edge must have two adjacent faces. 109 | * Each selected vertex must be on exactly on of the selected edges. 110 | * If left side vertex is selected, the third vertex must not touch any other component. 111 | */ 112 | 113 | bool getSymmetricalComponentSelection(MeshData &meshData, MSelectionList &selection, ComponentSelection &componentSelection, bool leftSideVertexSelected) 114 | { 115 | bool result = true; 116 | 117 | MDagPath mesh; 118 | MObject component; 119 | 120 | vector edgeIndices = vector(); 121 | vector faceIndices = vector(); 122 | vector vertexIndices = vector(); 123 | 124 | getSelectedComponentIndices(selection, edgeIndices, MFn::kMeshEdgeComponent); 125 | getSelectedComponentIndices(selection, faceIndices, MFn::kMeshPolygonComponent); 126 | getSelectedComponentIndices(selection, vertexIndices, MFn::kMeshVertComponent); 127 | 128 | int numberOfVerticesSelected = (int) vertexIndices.size(); 129 | 130 | if ( 131 | edgeIndices.size() == 2 && 132 | faceIndices.size() == 2 && 133 | (leftSideVertexSelected ? numberOfVerticesSelected == 3 : numberOfVerticesSelected == 2) 134 | ) { 135 | bool edge0NotOnBorder = meshData.edgeData[edgeIndices[0]].connectedFaces.size() > 1; 136 | bool edge1NotOnBorder = meshData.edgeData[edgeIndices[1]].connectedFaces.size() > 1; 137 | 138 | vector edgesOnFace0 = intersection(meshData.faceData[faceIndices[0]].connectedEdges, edgeIndices); 139 | vector edgesOnFace1 = intersection(meshData.faceData[faceIndices[1]].connectedEdges, edgeIndices); 140 | 141 | vector verticesOnEdge0 = intersection(meshData.edgeData[edgeIndices[0]].connectedVertices, vertexIndices); 142 | vector verticesOnEdge1 = intersection(meshData.edgeData[edgeIndices[1]].connectedVertices, vertexIndices); 143 | 144 | vector verticesOnFace0 = intersection(meshData.faceData[faceIndices[0]].connectedVertices, vertexIndices); 145 | vector verticesOnFace1 = intersection(meshData.faceData[faceIndices[1]].connectedVertices, vertexIndices); 146 | 147 | int leftSideVertex = -1; 148 | 149 | for (int &v : vertexIndices) 150 | { 151 | if (contains(verticesOnEdge0, v)) { continue; } 152 | if (contains(verticesOnEdge1, v)) { continue; } 153 | 154 | leftSideVertex = v; 155 | 156 | auto it0 = find(verticesOnFace0.begin(), verticesOnFace0.end(), leftSideVertex); 157 | 158 | if (it0 != verticesOnFace0.end()) { verticesOnFace0.erase(it0); } 159 | 160 | auto it1 = find(verticesOnFace1.begin(), verticesOnFace1.end(), leftSideVertex); 161 | 162 | if (it1 != verticesOnFace1.end()) { verticesOnFace1.erase(it1); } 163 | } 164 | 165 | bool leftSideVertexFound = leftSideVertexSelected ? leftSideVertex != -1 : true; 166 | 167 | bool edgesAreNotOnBorder = edge0NotOnBorder && edge1NotOnBorder; 168 | bool edgesAreOnFaces = (edgesOnFace0.size() == 1 && edgesOnFace1.size() == 1 && edgesOnFace0[0] != edgesOnFace1[0]); 169 | bool verticesAreOnFaces = (verticesOnFace0.size() == 1 && verticesOnFace1.size() == 1 && verticesOnFace0[0] != verticesOnFace1[0]); 170 | bool verticesAreOnEdges = (verticesOnEdge0.size() == 1 && verticesOnEdge1.size() == 1 && verticesOnEdge0[0] != verticesOnEdge1[0]); 171 | 172 | result = (edgesAreNotOnBorder && edgesAreOnFaces && verticesAreOnFaces && verticesAreOnEdges && leftSideVertexFound); 173 | 174 | if (result) 175 | { 176 | componentSelection.edgeIndices.first = edgeIndices[0]; 177 | componentSelection.edgeIndices.second = edgeIndices[1]; 178 | 179 | componentSelection.faceIndices.first = faceIndices[0]; 180 | componentSelection.faceIndices.second = faceIndices[1]; 181 | 182 | componentSelection.vertexIndices.first = verticesOnEdge0[0]; 183 | componentSelection.vertexIndices.second = verticesOnEdge1[0]; 184 | 185 | if (leftSideVertexFound) { 186 | componentSelection.leftVertexIndex = leftSideVertex; 187 | } 188 | } 189 | } else { 190 | result = false; 191 | } 192 | 193 | return result; 194 | } 195 | 196 | void getAllVertices(int &numberOfVertices, MObject &components) 197 | { 198 | MFnSingleIndexedComponent vertices; 199 | components = vertices.create(MFn::kMeshVertComponent); 200 | vertices.setCompleteData(numberOfVertices); 201 | } -------------------------------------------------------------------------------- /src/polySymmetryNode.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include "meshData.h" 7 | #include "polySymmetryNode.h" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | using namespace std; 26 | 27 | MObject PolySymmetryNode::numberOfEdges; 28 | MObject PolySymmetryNode::numberOfFaces; 29 | MObject PolySymmetryNode::numberOfVertices; 30 | 31 | MObject PolySymmetryNode::edgeSymmetry; 32 | MObject PolySymmetryNode::faceSymmetry; 33 | MObject PolySymmetryNode::vertexSymmetry; 34 | 35 | MObject PolySymmetryNode::edgeSides; 36 | MObject PolySymmetryNode::faceSides; 37 | MObject PolySymmetryNode::vertexSides; 38 | 39 | MObject PolySymmetryNode::vertexChecksum; 40 | 41 | PolySymmetryNode::PolySymmetryNode() {} 42 | PolySymmetryNode::~PolySymmetryNode() {} 43 | 44 | void* PolySymmetryNode::creator() 45 | { 46 | return new PolySymmetryNode(); 47 | } 48 | 49 | 50 | MStatus PolySymmetryNode::initialize() 51 | { 52 | MStatus status; 53 | 54 | MFnTypedAttribute t; 55 | MFnNumericAttribute n; 56 | 57 | numberOfEdges = n.create(NUMBER_OF_EDGES, "ne", MFnNumericData::kLong, -1, &status); 58 | CHECK_MSTATUS_AND_RETURN_IT(status); 59 | 60 | numberOfFaces = n.create(NUMBER_OF_FACES, "nf", MFnNumericData::kLong, -1, &status); 61 | CHECK_MSTATUS_AND_RETURN_IT(status); 62 | 63 | numberOfVertices = n.create(NUMBER_OF_VERTICES, "nv", MFnNumericData::kLong, -1, &status); 64 | CHECK_MSTATUS_AND_RETURN_IT(status); 65 | 66 | edgeSymmetry = t.create(EDGE_SYMMETRY, "esy", MFnData::kIntArray, MObject::kNullObj, &status); 67 | CHECK_MSTATUS_AND_RETURN_IT(status); 68 | 69 | faceSymmetry = t.create(FACE_SYMMETRY, "fsy", MFnData::kIntArray, MObject::kNullObj, &status); 70 | CHECK_MSTATUS_AND_RETURN_IT(status); 71 | 72 | vertexSymmetry = t.create(VERTEX_SYMMETRY, "vsy", MFnData::kIntArray, MObject::kNullObj, &status); 73 | CHECK_MSTATUS_AND_RETURN_IT(status); 74 | 75 | edgeSides = t.create(EDGE_SIDES, "es", MFnData::kIntArray, MObject::kNullObj, &status); 76 | CHECK_MSTATUS_AND_RETURN_IT(status); 77 | 78 | faceSides = t.create(FACE_SIDES, "fs", MFnData::kIntArray, MObject::kNullObj, &status); 79 | CHECK_MSTATUS_AND_RETURN_IT(status); 80 | 81 | vertexSides = t.create(VERTEX_SIDES, "vs", MFnData::kIntArray, MObject::kNullObj, &status); 82 | CHECK_MSTATUS_AND_RETURN_IT(status); 83 | 84 | vertexChecksum = n.create(VERTEX_CHECKSUM, "vc", MFnNumericData::kLong, -1, &status); 85 | CHECK_MSTATUS_AND_RETURN_IT(status); 86 | 87 | addAttribute(numberOfEdges); 88 | addAttribute(numberOfFaces); 89 | addAttribute(numberOfVertices); 90 | 91 | addAttribute(edgeSymmetry); 92 | addAttribute(faceSymmetry); 93 | addAttribute(vertexSymmetry); 94 | 95 | addAttribute(edgeSides); 96 | addAttribute(faceSides); 97 | addAttribute(vertexSides); 98 | 99 | addAttribute(vertexChecksum); 100 | 101 | return MStatus::kSuccess; 102 | } 103 | 104 | 105 | MStatus PolySymmetryNode::compute(const MPlug &plug, MDataBlock &dataBlock) 106 | { 107 | return MStatus::kSuccess; 108 | } 109 | 110 | 111 | MStatus PolySymmetryNode::setValue(MFnDependencyNode &fnNode, const char* attributeName, int &value) 112 | { 113 | MStatus status; 114 | 115 | MPlug plug = fnNode.findPlug(attributeName, false, &status); 116 | CHECK_MSTATUS_AND_RETURN_IT(status); 117 | 118 | status = plug.setValue(value); 119 | CHECK_MSTATUS_AND_RETURN_IT(status); 120 | 121 | return MStatus::kSuccess; 122 | } 123 | 124 | 125 | MStatus PolySymmetryNode::getValue(MFnDependencyNode &fnNode, const char* attributeName, int &value) 126 | { 127 | MStatus status; 128 | 129 | MPlug plug = fnNode.findPlug(attributeName, true, &status); 130 | CHECK_MSTATUS_AND_RETURN_IT(status); 131 | 132 | status = plug.getValue(value); 133 | CHECK_MSTATUS_AND_RETURN_IT(status); 134 | 135 | return MStatus::kSuccess; 136 | } 137 | 138 | 139 | MStatus PolySymmetryNode::setValues(MFnDependencyNode &fnNode, const char* attributeName, vector &values) 140 | { 141 | MStatus status; 142 | 143 | MPlug plug = fnNode.findPlug(attributeName, false, &status); 144 | CHECK_MSTATUS_AND_RETURN_IT(status); 145 | 146 | MIntArray valueArray; 147 | 148 | for (int &v : values) 149 | { 150 | valueArray.append(v); 151 | } 152 | 153 | MFnIntArrayData valueArrayData; 154 | 155 | MObject data = valueArrayData.create(valueArray, &status); 156 | CHECK_MSTATUS_AND_RETURN_IT(status); 157 | 158 | status = plug.setMObject(data); 159 | CHECK_MSTATUS_AND_RETURN_IT(status); 160 | 161 | return MStatus::kSuccess; 162 | } 163 | 164 | 165 | MStatus PolySymmetryNode::getValues(MFnDependencyNode &fnNode, const char* attributeName, vector &values) 166 | { 167 | MStatus status; 168 | 169 | MPlug plug = fnNode.findPlug(attributeName, true, &status); 170 | CHECK_MSTATUS_AND_RETURN_IT(status); 171 | 172 | MObject data = plug.asMObject(); 173 | 174 | MFnIntArrayData valueArrayData(data, &status); 175 | CHECK_MSTATUS_AND_RETURN_IT(status); 176 | 177 | MIntArray valueArray = valueArrayData.array(&status); 178 | CHECK_MSTATUS_AND_RETURN_IT(status); 179 | 180 | uint numberOfValues = valueArray.length(); 181 | values.resize((int) numberOfValues); 182 | 183 | for (uint i = 0; i < numberOfValues; i++) 184 | { 185 | values[i] = valueArray[i]; 186 | } 187 | 188 | return MStatus::kSuccess; 189 | } 190 | 191 | 192 | MStatus PolySymmetryNode::getCacheKey(MObject &node, string &key) 193 | { 194 | MStatus status; 195 | 196 | int numberOfEdges; 197 | int numberOfFaces; 198 | int numberOfVertices; 199 | int vertexChecksum; 200 | 201 | MFnDependencyNode fnNode(node); 202 | 203 | PolySymmetryNode::getValue(fnNode, NUMBER_OF_EDGES, numberOfEdges); 204 | PolySymmetryNode::getValue(fnNode, NUMBER_OF_FACES, numberOfFaces); 205 | PolySymmetryNode::getValue(fnNode, NUMBER_OF_VERTICES, numberOfVertices); 206 | PolySymmetryNode::getValue(fnNode, VERTEX_CHECKSUM, vertexChecksum); 207 | 208 | if (numberOfEdges == -1 || numberOfFaces == -1 || numberOfVertices == -1 || vertexChecksum == -1) 209 | { 210 | MString cacheErrorMessage("Cannot cache ^1s because it has incomplete data. Delete it and try again."); 211 | cacheErrorMessage.format(cacheErrorMessage, fnNode.name()); 212 | 213 | MGlobal::displayWarning(cacheErrorMessage); 214 | } else { 215 | key = to_string(numberOfEdges) + ":" + 216 | to_string(numberOfFaces) + ":" + 217 | to_string(numberOfVertices) + ":" + 218 | to_string(vertexChecksum); 219 | } 220 | 221 | return MStatus::kSuccess; 222 | } 223 | 224 | 225 | MStatus PolySymmetryNode::getCacheKeyFromMesh(MDagPath &dagPath, string &key) 226 | { 227 | MStatus status; 228 | 229 | MFnMesh fnMesh(dagPath); 230 | 231 | int numberOfEdges = fnMesh.numEdges(); 232 | int numberOfFaces = fnMesh.numPolygons(); 233 | int numberOfVertices = fnMesh.numVertices(); 234 | 235 | int vertexChecksum = (int) MeshData::getVertexChecksum(dagPath); 236 | 237 | key = to_string(numberOfEdges) + ":" 238 | + to_string(numberOfFaces) + ":" 239 | + to_string(numberOfVertices) + ":" 240 | + to_string(vertexChecksum); 241 | 242 | return MStatus::kSuccess; 243 | } -------------------------------------------------------------------------------- /src/polySymmetryTool.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include "polySymmetryTool.h" 7 | #include "polySymmetryCmd.h" 8 | #include "selection.h" 9 | #include "sceneCache.h" 10 | #include "util.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | using namespace std; 27 | 28 | PolySymmetryTool::PolySymmetryTool() {} 29 | 30 | PolySymmetryTool::~PolySymmetryTool() 31 | { 32 | selectedComponents.clear(); 33 | leftSideVertexIndices.clear(); 34 | } 35 | 36 | void PolySymmetryTool::toolOnSetup(MEvent &event) 37 | { 38 | meshData = MeshData(); 39 | 40 | this->getSelectedMesh(); 41 | this->updateHelpString(); 42 | } 43 | 44 | void PolySymmetryTool::toolOffCleanup() 45 | { 46 | 47 | } 48 | 49 | MStatus PolySymmetryTool::helpStateHasChanged(MEvent &event) 50 | { 51 | this->updateHelpString(); 52 | 53 | return MStatus::kSuccess; 54 | } 55 | 56 | void PolySymmetryTool::deleteAction() 57 | { 58 | if (selectedComponents.empty()) 59 | { 60 | this->clearSelectedMesh(); 61 | } else { 62 | selectedComponents.pop_back(); 63 | leftSideVertexIndices.pop_back(); 64 | } 65 | 66 | if (selectedMesh.isValid()) 67 | { 68 | this->symmetryData.reset(); 69 | } else { 70 | this->symmetryData.clear(); 71 | } 72 | 73 | this->updateHelpString(); 74 | this->recalculateSymmetry(); 75 | this->updateDisplayColors(); 76 | } 77 | 78 | void PolySymmetryTool::abortAction() 79 | { 80 | this->clearSelectedMesh(); 81 | MGlobal::executeCommand("escapeCurrentTool"); 82 | } 83 | 84 | void PolySymmetryTool::completeAction() 85 | { 86 | bool continueSelecting = true; 87 | 88 | if (!selectedMesh.isValid()) 89 | { 90 | this->getSelectedMesh(); 91 | } else { 92 | MSelectionList activeSelection; 93 | MGlobal::getActiveSelectionList(activeSelection); 94 | 95 | if (activeSelection.isEmpty()) 96 | { 97 | continueSelecting = false; 98 | } else { 99 | ComponentSelection s; 100 | 101 | MSelectionList selection; 102 | 103 | getSelectedComponents(selectedMesh, activeSelection, selection, MFn::Type::kMeshEdgeComponent); 104 | getSelectedComponents(selectedMesh, activeSelection, selection, MFn::Type::kMeshVertComponent); 105 | getSelectedComponents(selectedMesh, activeSelection, selection, MFn::Type::kMeshPolygonComponent); 106 | 107 | bool isValidSelection = getSymmetricalComponentSelection(meshData, selection, s, true); 108 | 109 | if (isValidSelection) 110 | { 111 | this->selectedComponents.push_back(s); 112 | this->leftSideVertexIndices.push_back(s.leftVertexIndex); 113 | MGlobal::clearSelectionList(); 114 | } else { 115 | MGlobal::displayError("Must select a symmetrical edge, face, and vertex on both sides of the mesh, and a lone vertex on the left side of the mesh."); 116 | this->updateHelpString(); 117 | return; 118 | } 119 | } 120 | } 121 | 122 | if (continueSelecting) 123 | { 124 | this->updateHelpString(); 125 | this->recalculateSymmetry(); 126 | this->updateDisplayColors(); 127 | } else { 128 | bool selectionIsComplete = true; 129 | 130 | if (!selectedMesh.isValid()) 131 | { 132 | MGlobal::displayError("Select a mesh to calculate the symmetry of."); 133 | selectionIsComplete = false; 134 | } 135 | 136 | if (selectedComponents.empty()) 137 | { 138 | MGlobal::displayError("Must select at least set of symmetrical components."); 139 | selectionIsComplete = false; 140 | } 141 | 142 | if (selectionIsComplete) 143 | { 144 | PolySymmetryCommand *cmd = (PolySymmetryCommand*) newToolCommand(); 145 | (*cmd).finalize(); 146 | 147 | doToolCommand(); 148 | clearSelectedMesh(); 149 | } 150 | } 151 | } 152 | 153 | void PolySymmetryTool::doToolCommand() 154 | { 155 | MGlobal::executeCommand("escapeCurrentTool"); 156 | 157 | stringstream ss; 158 | ss << PolySymmetryCommand::COMMAND_NAME << ' '; 159 | 160 | for (ComponentSelection &s : selectedComponents) 161 | { 162 | ss << SYMMETRY_COMPONENTS_FLAG << ' '; 163 | 164 | ss << selectedMesh.partialPathName() << ".f[" << s.faceIndices.first << "] "; 165 | ss << selectedMesh.partialPathName() << ".f[" << s.faceIndices.second << "] "; 166 | 167 | ss << selectedMesh.partialPathName() << ".e[" << s.edgeIndices.first << "] "; 168 | ss << selectedMesh.partialPathName() << ".e[" << s.edgeIndices.second << "] "; 169 | 170 | ss << selectedMesh.partialPathName() << ".vtx[" << s.vertexIndices.first << "] "; 171 | ss << selectedMesh.partialPathName() << ".vtx[" << s.vertexIndices.second << "] "; 172 | 173 | ss << LEFT_SIDE_VERTEX_FLAG << ' '; 174 | 175 | ss << selectedMesh.partialPathName() << ".vtx[" << s.leftVertexIndex << "] "; 176 | } 177 | 178 | ss << selectedMesh.partialPathName() << ';'; 179 | 180 | MGlobal::executeCommand(ss.str().c_str(), true); 181 | } 182 | 183 | MStatus PolySymmetryTool::getSelectedMesh() 184 | { 185 | MStatus status; 186 | 187 | MSelectionList activeSelection; 188 | MGlobal::getActiveSelectionList(activeSelection); 189 | 190 | if (!activeSelection.isEmpty()) 191 | { 192 | MItSelectionList iterSelection(activeSelection); 193 | MDagPath object; 194 | 195 | while (!iterSelection.isDone()) 196 | { 197 | iterSelection.getDagPath(object); 198 | 199 | if (object.hasFn(MFn::kMesh)) 200 | { 201 | this->selectedMesh.set(object); 202 | break; 203 | } 204 | } 205 | } 206 | 207 | if (this->selectedMesh.isValid()) 208 | { 209 | MObject obj; 210 | bool cacheHit = PolySymmetryCache::getNodeFromCache(this->selectedMesh, obj); 211 | 212 | if (cacheHit) 213 | { 214 | MString warningMsg("Symmetry data already exists for ^1s - ^2s."); 215 | warningMsg.format(warningMsg, selectedMesh.partialPathName(), MFnDependencyNode(obj).name()); 216 | 217 | MGlobal::displayError(warningMsg); 218 | 219 | this->abortAction(); 220 | return MStatus::kFailure; 221 | } 222 | 223 | symmetryData.initialize(selectedMesh); 224 | meshData.unpackMesh(selectedMesh); 225 | MFnMesh meshFn(selectedMesh); 226 | 227 | status = meshFn.getVertexColors(originalVertexColors); 228 | meshFn.clearColors(); 229 | 230 | MPlug displayColorsPlug = meshFn.findPlug("displayColors"); 231 | 232 | originalDisplayColors = displayColorsPlug.asBool(); 233 | status = displayColorsPlug.setBool(true); 234 | CHECK_MSTATUS_AND_RETURN_IT(status); 235 | 236 | if (selectedMesh.node().hasFn(MFn::kMesh)) 237 | { 238 | selectedMesh.pop(); 239 | } 240 | } 241 | 242 | return MStatus::kSuccess; 243 | } 244 | 245 | MStatus PolySymmetryTool::clearSelectedMesh() 246 | { 247 | MStatus status; 248 | 249 | if (selectedMesh.isValid()) 250 | { 251 | MFnMesh meshFn(selectedMesh); 252 | 253 | status = meshFn.findPlug("displayColors").setBool(originalDisplayColors); 254 | CHECK_MSTATUS_AND_RETURN_IT(status); 255 | 256 | selectedMesh.set(MDagPath()); 257 | meshData.clear(); 258 | } 259 | 260 | status = displayColorModifier.undoIt(); 261 | CHECK_MSTATUS_AND_RETURN_IT(status); 262 | 263 | return MStatus::kSuccess; 264 | 265 | } 266 | 267 | void PolySymmetryTool::updateHelpString() 268 | { 269 | if (selectedMesh.isValid()) 270 | { 271 | MDagPath parent = MDagPath(selectedMesh); 272 | parent.pop(); 273 | 274 | this->setHelpString("Select components on " + parent.partialPathName() + " that define the symmetry and press ENTER."); 275 | } else { 276 | this->setHelpString("Select a mesh to find the symmetry on and press ENTER"); 277 | } 278 | } 279 | 280 | void PolySymmetryTool::recalculateSymmetry() 281 | { 282 | if (!selectedMesh.isValid()) { return; } 283 | 284 | for (ComponentSelection &s : selectedComponents) 285 | { 286 | this->symmetryData.findSymmetricalVertices(s); 287 | } 288 | 289 | this->symmetryData.findVertexSides(this->leftSideVertexIndices); 290 | } 291 | 292 | void PolySymmetryTool::updateDisplayColors() 293 | { 294 | if (!selectedMesh.isValid()) { return; } 295 | 296 | MIntArray vertexList(meshData.numberOfVertices); 297 | MColorArray colors(meshData.numberOfVertices); 298 | 299 | for (int i = 0; i < meshData.numberOfVertices; i++) 300 | { 301 | float r = 0.5f; 302 | float g = 0.5f; 303 | float b = 0.5f; 304 | 305 | if (this->symmetryData.vertexSides[i] == 1) 306 | { 307 | b = 0.75f; 308 | } else if (this->symmetryData.vertexSides[i] == -1) { 309 | r = 0.75f; 310 | } 311 | 312 | vertexList[i] = i; 313 | colors[i] = MColor(r, g, b); 314 | } 315 | 316 | MFnMesh meshFn(selectedMesh); 317 | meshFn.setVertexColors(colors, vertexList, &displayColorModifier); 318 | } 319 | 320 | PolySymmetryContextCmd::PolySymmetryContextCmd() {} 321 | PolySymmetryContextCmd::~PolySymmetryContextCmd() {} 322 | 323 | MPxContext* PolySymmetryContextCmd::makeObj() 324 | { 325 | return new PolySymmetryTool(); 326 | } 327 | 328 | void* PolySymmetryContextCmd::creator() 329 | { 330 | return new PolySymmetryContextCmd(); 331 | } -------------------------------------------------------------------------------- /src/polyDeformerWeights.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include "parseArgs.h" 10 | #include "polyDeformerWeights.h" 11 | #include "polySymmetryNode.h" 12 | #include "sceneCache.h" 13 | #include "selection.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | using namespace std; 32 | 33 | // Indicates the source deformer. 34 | #define SOURCE_DEFORMER_FLAG "-sd" 35 | #define SOURCE_DEFORMER_LONG_FLAG "-sourceDeformer" 36 | 37 | // Indicates the source deformed mesh. 38 | #define SOURCE_MESH_FLAG "-sm" 39 | #define SOURCE_MESH_LONG_FLAG "-sourceMesh" 40 | 41 | // Indicates the destination deformer. 42 | #define DESTINATION_DEFORMER_FLAG "-dd" 43 | #define DESTINATION_DEFORMER_LONG_FLAG "-destinationDeformer" 44 | 45 | // Indicates the destination deformed shape. 46 | #define DESTINATION_MESH_FLAG "-dm" 47 | #define DESTINATION_MESH_LONG_FLAG "-destinationMesh" 48 | 49 | // Indicates the direction of the mirror or flip action - 1 (left to right) or -1 (right to left) 50 | #define DIRECTION_FLAG "-d" 51 | #define DIRECTION_LONG_FLAG "-direction" 52 | 53 | // Indicates that the deformer weights should be mirrored. 54 | #define MIRROR_FLAG "-m" 55 | #define MIRROR_LONG_FLAG "-mirror" 56 | 57 | // Indicates that the deformer weights should be flipped. 58 | #define FLIP_FLAG "-f" 59 | #define FLIP_LONG_FLAG "-flip" 60 | 61 | #define RETURN_IF_ERROR(s) if (!s) { return s; } 62 | 63 | PolyDeformerWeightsCommand::PolyDeformerWeightsCommand() {} 64 | PolyDeformerWeightsCommand::~PolyDeformerWeightsCommand() {} 65 | 66 | void* PolyDeformerWeightsCommand::creator() 67 | { 68 | return new PolyDeformerWeightsCommand(); 69 | } 70 | 71 | MSyntax PolyDeformerWeightsCommand::getSyntax() 72 | { 73 | MSyntax syntax; 74 | 75 | syntax.addFlag(SOURCE_DEFORMER_FLAG, SOURCE_DEFORMER_LONG_FLAG, MSyntax::kSelectionItem); 76 | syntax.addFlag(SOURCE_MESH_FLAG, SOURCE_MESH_LONG_FLAG, MSyntax::kSelectionItem); 77 | 78 | syntax.addFlag(DESTINATION_DEFORMER_FLAG, DESTINATION_DEFORMER_LONG_FLAG, MSyntax::kSelectionItem); 79 | syntax.addFlag(DESTINATION_MESH_FLAG, DESTINATION_MESH_LONG_FLAG, MSyntax::kSelectionItem); 80 | 81 | syntax.addFlag(DIRECTION_FLAG, DIRECTION_LONG_FLAG, MSyntax::kLong); 82 | 83 | syntax.addFlag(MIRROR_FLAG, MIRROR_LONG_FLAG); 84 | syntax.addFlag(FLIP_FLAG, FLIP_LONG_FLAG); 85 | 86 | syntax.enableEdit(false); 87 | syntax.enableQuery(false); 88 | 89 | return syntax; 90 | } 91 | 92 | MStatus PolyDeformerWeightsCommand::parseArguments(MArgDatabase &argsData) 93 | { 94 | MStatus status; 95 | 96 | status = parseArgs::getNodeArgument(argsData, SOURCE_DEFORMER_FLAG, this->sourceDeformer, true); 97 | RETURN_IF_ERROR(status); 98 | 99 | status = parseArgs::getNodeArgument(argsData, DESTINATION_DEFORMER_FLAG, this->destinationDeformer, false); 100 | RETURN_IF_ERROR(status); 101 | 102 | status = parseArgs::getDagPathArgument(argsData, SOURCE_MESH_FLAG, this->sourceMesh, true); 103 | RETURN_IF_ERROR(status); 104 | 105 | status = parseArgs::getDagPathArgument(argsData, DESTINATION_MESH_FLAG, this->destinationMesh, false); 106 | RETURN_IF_ERROR(status); 107 | 108 | this->mirrorWeights = argsData.isFlagSet(MIRROR_FLAG); 109 | this->flipWeights = argsData.isFlagSet(FLIP_FLAG); 110 | 111 | status = argsData.getFlagArgument(DIRECTION_FLAG, 0, this->direction); 112 | 113 | return MStatus::kSuccess; 114 | } 115 | 116 | MStatus PolyDeformerWeightsCommand::validateArguments() 117 | { 118 | MStatus status; 119 | 120 | if (this->direction != 1 && this->direction != -1) 121 | { 122 | MString errorMsg("^1s/^2s flag should be 1 (left to right) or -1 (right to left)"); 123 | errorMsg.format(errorMsg, MString(DIRECTION_LONG_FLAG), MString(DIRECTION_FLAG)); 124 | 125 | MGlobal::displayError(errorMsg); 126 | return MStatus::kFailure; 127 | } 128 | 129 | if (!parseArgs::isNodeType(sourceDeformer, MFn::kWeightGeometryFilt)) 130 | { 131 | MString errorMsg("A deformer node should be specified with the ^1s/^2s flag."); 132 | errorMsg.format(errorMsg, MString(SOURCE_DEFORMER_LONG_FLAG), MString(SOURCE_DEFORMER_FLAG)); 133 | 134 | MGlobal::displayError(errorMsg); 135 | return MStatus::kFailure; 136 | } 137 | 138 | if (destinationDeformer.isNull()) 139 | { 140 | destinationDeformer = sourceDeformer; 141 | } else if (!destinationDeformer.hasFn(MFn::kWeightGeometryFilt)) { 142 | MString errorMsg("A deformer node should be specified with the ^1s/^2s flag."); 143 | errorMsg.format(errorMsg, MString(DESTINATION_DEFORMER_LONG_FLAG), MString(DESTINATION_DEFORMER_FLAG)); 144 | 145 | MGlobal::displayError(errorMsg); 146 | return MStatus::kFailure; 147 | } 148 | 149 | if (!parseArgs::isNodeType(this->sourceMesh, MFn::kMesh)) 150 | { 151 | MString errorMsg("A mesh node should be specified with the ^1s/^2s flag."); 152 | errorMsg.format(errorMsg, MString(SOURCE_MESH_LONG_FLAG), MString(SOURCE_MESH_FLAG)); 153 | 154 | MGlobal::displayError(errorMsg); 155 | return MStatus::kFailure; 156 | } 157 | 158 | if (destinationMesh.node().isNull()) 159 | { 160 | destinationMesh.set(sourceMesh); 161 | } else if (!destinationMesh.hasFn(MFn::kMesh)) { 162 | MString errorMsg("A mesh node should be specified with the ^1s/^2s flag."); 163 | errorMsg.format(errorMsg, MString(DESTINATION_MESH_LONG_FLAG), MString(DESTINATION_MESH_FLAG)); 164 | 165 | MGlobal::displayError(errorMsg); 166 | return MStatus::kFailure; 167 | } else { 168 | MFnMesh fnSourceMesh(this->sourceMesh); 169 | MFnMesh fnDestinationMesh(this->destinationMesh); 170 | 171 | if (fnSourceMesh.numVertices() != fnDestinationMesh.numVertices()) 172 | { 173 | MString errorMsg("Source mesh and destination mesh are not point compatible. Cannot continue."); 174 | MGlobal::displayError(errorMsg); 175 | return MStatus::kFailure; 176 | } 177 | } 178 | 179 | MSelectionList activeSelection; 180 | MSelectionList vertexSelection; 181 | 182 | MGlobal::getActiveSelectionList(activeSelection); 183 | 184 | if (destinationMesh.node().hasFn(MFn::kMesh)) { destinationMesh.pop(); } 185 | getSelectedComponents(destinationMesh, activeSelection, vertexSelection, MFn::Type::kMeshVertComponent); 186 | 187 | if (!vertexSelection.isEmpty()) 188 | { 189 | vertexSelection.getDagPath(0, destinationMesh, components); 190 | } 191 | 192 | if (!sourceMesh.node().hasFn(MFn::kMesh)) 193 | { 194 | sourceMesh.extendToShapeDirectlyBelow(0); 195 | } 196 | 197 | if (!destinationMesh.node().hasFn(MFn::kMesh)) 198 | { 199 | destinationMesh.extendToShapeDirectlyBelow(0); 200 | } 201 | 202 | if (mirrorWeights || flipWeights) 203 | { 204 | bool cacheHit = PolySymmetryCache::getNodeFromCache(this->sourceMesh, this->polySymmetryData); 205 | 206 | if (!cacheHit) 207 | { 208 | MString errorMsg("Mesh specified with the ^1s/^2s flag must have an associated ^3s node."); 209 | errorMsg.format(errorMsg, MString(SOURCE_MESH_LONG_FLAG), MString(SOURCE_MESH_FLAG), PolySymmetryNode::NODE_NAME); 210 | 211 | MGlobal::displayError(errorMsg); 212 | return MStatus::kFailure; 213 | } 214 | } 215 | 216 | return MStatus::kSuccess; 217 | } 218 | 219 | MStatus PolyDeformerWeightsCommand::doIt(const MArgList& argList) 220 | { 221 | MStatus status; 222 | 223 | MArgDatabase argsData(syntax(), argList, &status); 224 | RETURN_IF_ERROR(status); 225 | 226 | status = this->parseArguments(argsData); 227 | RETURN_IF_ERROR(status); 228 | 229 | status = this->validateArguments(); 230 | RETURN_IF_ERROR(status); 231 | 232 | return this->redoIt(); 233 | } 234 | 235 | MStatus PolyDeformerWeightsCommand::redoIt() 236 | { 237 | MStatus status; 238 | 239 | MFnWeightGeometryFilter fnSourceDeformer(this->sourceDeformer, &status); 240 | MFnWeightGeometryFilter fnDestinationDeformer(this->destinationDeformer, &status); 241 | 242 | MObjectArray sourceOutputGeometry; 243 | MObjectArray destinationOutputGeometry; 244 | 245 | int numberOfVertices = MFnMesh(this->sourceMesh).numVertices(); 246 | 247 | MObject sourceComponents; 248 | MObject destinationComponents; 249 | 250 | getAllVertices(numberOfVertices, sourceComponents); 251 | getAllVertices(numberOfVertices, destinationComponents); 252 | 253 | MFloatArray sourceWeights(numberOfVertices); 254 | MFloatArray destinationWeights(numberOfVertices); 255 | 256 | oldWeightValues.setLength(numberOfVertices); 257 | 258 | uint sourceGeometryIndex = fnSourceDeformer.indexForOutputShape(this->sourceMesh.node(), &status); 259 | 260 | if (!status) 261 | { 262 | MString errorMsg("Source mesh ^1s is not deformed by deformer ^2s."); 263 | errorMsg.format(errorMsg, this->sourceMesh.partialPathName(), MFnDependencyNode(this->sourceDeformer).name()); 264 | 265 | MGlobal::displayError(errorMsg); 266 | return MStatus::kFailure; 267 | } 268 | 269 | uint destinationGeometryIndex = fnDestinationDeformer.indexForOutputShape(this->destinationMesh.node(), &status); 270 | 271 | if (!status) 272 | { 273 | MString errorMsg("Destination mesh ^1s is not deformed by deformer ^2s."); 274 | errorMsg.format(errorMsg, this->destinationMesh.partialPathName(), MFnDependencyNode(this->destinationDeformer).name()); 275 | 276 | MGlobal::displayError(errorMsg); 277 | return MStatus::kFailure; 278 | } 279 | 280 | status = fnSourceDeformer.getWeights(sourceGeometryIndex, sourceComponents, sourceWeights); 281 | CHECK_MSTATUS_AND_RETURN_IT(status); 282 | 283 | status = fnDestinationDeformer.getWeights(destinationGeometryIndex, destinationComponents, oldWeightValues); 284 | CHECK_MSTATUS_AND_RETURN_IT(status); 285 | 286 | destinationWeights.copy(sourceWeights); 287 | 288 | if (mirrorWeights || flipWeights) 289 | { 290 | vector vertexSymmetry; 291 | vector vertexSides; 292 | 293 | MFnDependencyNode fnNode(polySymmetryData); 294 | 295 | PolySymmetryNode::getValues(fnNode, VERTEX_SYMMETRY, vertexSymmetry); 296 | PolySymmetryNode::getValues(fnNode, VERTEX_SIDES, vertexSides); 297 | 298 | MItGeometry itGeo(destinationMesh, components); 299 | 300 | bool useVertexSelection = !components.isNull(); 301 | 302 | while (!itGeo.isDone()) 303 | { 304 | int i = itGeo.index(); 305 | int o = vertexSymmetry[i]; 306 | 307 | float wti = this->getWeight(i, sourceWeights, vertexSymmetry, vertexSides); 308 | destinationWeights.set(wti, i); 309 | 310 | if (useVertexSelection) 311 | { 312 | float wto = this->getWeight(o, sourceWeights, vertexSymmetry, vertexSides); 313 | destinationWeights.set(wto, o); 314 | } 315 | 316 | itGeo.next(); 317 | } 318 | } 319 | 320 | status = fnDestinationDeformer.setWeight(this->destinationMesh, destinationGeometryIndex, destinationComponents, destinationWeights); 321 | CHECK_MSTATUS_AND_RETURN_IT(status); 322 | 323 | this->geometryIndex = destinationGeometryIndex; 324 | this->components = destinationComponents; 325 | 326 | return MStatus::kSuccess; 327 | } 328 | 329 | float PolyDeformerWeightsCommand::getWeight(int vertexIndex, MFloatArray &sourceWeights, vector &vertexSymmetry, vector vertexSides) 330 | { 331 | int i = vertexIndex; 332 | int o = vertexSymmetry[i]; 333 | float wt = sourceWeights[i]; 334 | 335 | if (flipWeights || (mirrorWeights && vertexSides[i] != direction)) 336 | { 337 | wt = sourceWeights[o]; 338 | } 339 | 340 | return wt; 341 | } 342 | 343 | 344 | MStatus PolyDeformerWeightsCommand::undoIt() 345 | { 346 | MStatus status; 347 | 348 | MFnWeightGeometryFilter fnDeformer(this->destinationDeformer, &status); 349 | 350 | status = fnDeformer.setWeight( 351 | this->destinationMesh, 352 | this->geometryIndex, 353 | this->components, 354 | this->oldWeightValues 355 | ); 356 | 357 | CHECK_MSTATUS_AND_RETURN_IT(status); 358 | 359 | return MStatus::kSuccess; 360 | } -------------------------------------------------------------------------------- /src/polySymmetry.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | /* 7 | The poly symmetry algorithm traverses the topology of a polygon mesh to 8 | calculate the symmetry of its components. It does this by examining pairs of 9 | edges that are known to be symmetrical, walking the vertices of the faces 10 | adjacent to the edges, establishing a symmetry table. 11 | 12 | Because an edge may have two adjacent faces, the user must provide starting 13 | data for the algorithm in the form of a pair of symmetrical faces, edges, 14 | and vertices. The edges must be on the faces, and the vertices must be on 15 | the edges. 16 | 17 | From this initial condition, we can assume that the other vertices 18 | on the edges are symmetrical. Once an edge, the the vertices on it, and one 19 | of the faces adjacent to it have had their symmetry computed, the rest of the 20 | components on the shell can have their symmetry computed thus: 21 | 22 | > Start on an edge with known symmetry, whose vertices have known 23 | symmetry, and is on a face with known symmetry. 24 | > Get the other face connected to the edge - its symmetry is unknown. 25 | > Starting at either vertex on the edge, walk to its other neighboring 26 | vertex on the face. A neighboring vertex is a vertex on the same face 27 | connected by an edge. On the edge that is symmetrical to this edge, 28 | do the same. These neighboring vertices are symmetrical, as is the edge 29 | that was traversed to find them. 30 | > Repeat until all the vertices on the face have had their 31 | symmetry computed. 32 | > Repeat the algorithm, starting at each pair of traversed edges, until 33 | the symmetry of all components on this shell are computed. 34 | */ 35 | 36 | // TODO - unintuitive results returned if the mesh does not have a center edge loop whose vertices are symmetrical to themselves. 37 | 38 | #include "meshData.h" 39 | #include "polySymmetry.h" 40 | #include "selection.h" 41 | #include "util.h" 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #include 49 | #include 50 | 51 | using namespace std; 52 | 53 | 54 | PolySymmetryData::PolySymmetryData() 55 | { 56 | MeshData meshData = MeshData(); 57 | 58 | examinedEdges = vector(); 59 | examinedFaces = vector(); 60 | examinedVertices = vector(); 61 | 62 | vertexSymmetryIndices = vector(); 63 | edgeSymmetryIndices = vector(); 64 | faceSymmetryIndices = vector(); 65 | 66 | vertexSides = vector(); 67 | edgeSides = vector(); 68 | faceSides = vector(); 69 | 70 | leftSideVertexIndices = vector(); 71 | } 72 | 73 | 74 | PolySymmetryData::~PolySymmetryData() 75 | { 76 | this->clear(); 77 | } 78 | 79 | 80 | void PolySymmetryData::initialize(MDagPath &mesh) 81 | { 82 | meshData.unpackMesh(mesh); 83 | this->reset(); 84 | } 85 | 86 | 87 | void PolySymmetryData::clear() 88 | { 89 | meshData.clear(); 90 | this->reset(); 91 | } 92 | 93 | 94 | void PolySymmetryData::reset() 95 | { 96 | examinedEdges.clear(); 97 | examinedFaces.clear(); 98 | examinedVertices.clear(); 99 | 100 | edgeSymmetryIndices.clear(); 101 | faceSymmetryIndices.clear(); 102 | vertexSymmetryIndices.clear(); 103 | 104 | edgeSides.clear(); 105 | faceSides.clear(); 106 | vertexSides.clear(); 107 | 108 | leftSideVertexIndices.clear(); 109 | 110 | examinedEdges.resize(meshData.numberOfEdges, false); 111 | examinedFaces.resize(meshData.numberOfFaces, false); 112 | examinedVertices.resize(meshData.numberOfVertices, false); 113 | 114 | edgeSymmetryIndices.resize(meshData.numberOfEdges, -1); 115 | faceSymmetryIndices.resize(meshData.numberOfFaces, -1); 116 | vertexSymmetryIndices.resize(meshData.numberOfVertices, -1); 117 | 118 | edgeSides.resize(meshData.numberOfEdges, -1); 119 | faceSides.resize(meshData.numberOfFaces, -1); 120 | vertexSides.resize(meshData.numberOfVertices, -1); 121 | } 122 | 123 | 124 | void PolySymmetryData::findSymmetricalVertices(ComponentSelection &selection) 125 | { 126 | queue> symmetricalEdgesQueue = queue>(); 127 | symmetricalEdgesQueue.push(selection.edgeIndices); 128 | 129 | if (selection.leftVertexIndex != -1) 130 | { 131 | leftSideVertexIndices.push_back(selection.leftVertexIndex); 132 | } 133 | 134 | this->findFirstSymmetricalVertices(selection); 135 | 136 | while (!symmetricalEdgesQueue.empty()) 137 | { 138 | pair edgePair = symmetricalEdgesQueue.front(); 139 | symmetricalEdgesQueue.pop(); 140 | 141 | markSymmetricalEdges(edgePair.first, edgePair.second); 142 | 143 | pair facesPair = getUnexaminedFaces(edgePair); 144 | 145 | if (facesPair.first == -1 || facesPair.second == -1) 146 | { 147 | continue; 148 | } 149 | 150 | markSymmetricalFaces(facesPair.first, facesPair.second); 151 | 152 | this->findSymmetricalVerticesOnFace(facesPair); 153 | this->findSymmetricalEdgesOnFace(symmetricalEdgesQueue, facesPair.first); 154 | } 155 | } 156 | 157 | 158 | void PolySymmetryData::findFirstSymmetricalVertices(ComponentSelection &selection) 159 | { 160 | markSymmetricalVertices(selection.vertexIndices.first, selection.vertexIndices.second); 161 | markSymmetricalFaces(selection.faceIndices.first, selection.faceIndices.second); 162 | 163 | int vertex0 = meshData.edgeData[selection.edgeIndices.first].connectedVertices[0]; 164 | int vertex1 = meshData.edgeData[selection.edgeIndices.first].connectedVertices[1]; 165 | 166 | int nextVertex0 = examinedVertices[vertex0] ? vertex1 : vertex0; 167 | 168 | vertex0 = meshData.edgeData[selection.edgeIndices.second].connectedVertices[0]; 169 | vertex1 = meshData.edgeData[selection.edgeIndices.second].connectedVertices[1]; 170 | 171 | int nextVertex1 = examinedVertices[vertex0] ? vertex1 : vertex0; 172 | 173 | markSymmetricalVertices(nextVertex0, nextVertex1); 174 | 175 | this->findSymmetricalVerticesOnFace(selection.faceIndices); 176 | } 177 | 178 | 179 | pair PolySymmetryData::getUnexaminedFaces(pair &edgePair) 180 | { 181 | vector sharedFaces = intersection( 182 | meshData.edgeData[edgePair.first].connectedFaces, 183 | meshData.edgeData[edgePair.second].connectedFaces 184 | ); 185 | 186 | int face0 = -1; 187 | int face1 = -1; 188 | 189 | if (sharedFaces.size() > 0) 190 | { 191 | for (int &faceIndex : sharedFaces) 192 | { 193 | if (this->examinedFaces[faceIndex]) 194 | { 195 | continue; 196 | } 197 | 198 | if (face0 == -1) 199 | { 200 | face0 = faceIndex; 201 | continue; 202 | } 203 | 204 | if (face1 == -1) 205 | { 206 | face1 = faceIndex; 207 | continue; 208 | } 209 | } 210 | } else { 211 | face0 = getUnexaminedFace(edgePair.first); 212 | face1 = getUnexaminedFace(edgePair.second); 213 | } 214 | 215 | return pair(face0, face1); 216 | } 217 | 218 | 219 | int PolySymmetryData::getUnexaminedFace(int &edgeIndex) 220 | { 221 | int result = -1; 222 | 223 | for (int &faceIndex : meshData.edgeData[edgeIndex].connectedFaces) 224 | { 225 | if (!examinedFaces[faceIndex]) 226 | { 227 | result = faceIndex; 228 | break; 229 | } 230 | } 231 | 232 | return result; 233 | } 234 | 235 | 236 | void PolySymmetryData::findSymmetricalVerticesOnFace(pair &facePair) 237 | { 238 | queue faceVerticesQueue = queue(); 239 | 240 | for (int &v : meshData.faceData[facePair.first].connectedVertices) 241 | { 242 | if (examinedVertices[v]) 243 | { 244 | faceVerticesQueue.push(v); 245 | } 246 | } 247 | 248 | while (!faceVerticesQueue.empty()) 249 | { 250 | int vertex0 = faceVerticesQueue.front(); 251 | faceVerticesQueue.pop(); 252 | 253 | int vertex1 = vertexSymmetryIndices[vertex0]; 254 | 255 | int nextVertex0 = getUnexaminedVertexSibling(vertex0, facePair.first); 256 | int nextVertex1 = getUnexaminedVertexSibling(vertex1, facePair.second); 257 | 258 | if (nextVertex0 == -1 || nextVertex1 == -1) 259 | { 260 | continue; 261 | } 262 | 263 | markSymmetricalVertices(nextVertex0, nextVertex1); 264 | 265 | faceVerticesQueue.push(nextVertex0); 266 | } 267 | } 268 | 269 | 270 | void PolySymmetryData::findSymmetricalEdgesOnFace(queue> &symmetricalEdgesQueue, int &faceIndex) 271 | { 272 | int vertex0; 273 | int vertex1; 274 | 275 | int edgeIndex; 276 | 277 | for (int &e : meshData.faceData[faceIndex].connectedEdges) 278 | { 279 | if (examinedEdges[e]) { continue; } 280 | 281 | vertex0 = vertexSymmetryIndices[meshData.edgeData[e].connectedVertices[0]]; 282 | vertex1 = vertexSymmetryIndices[meshData.edgeData[e].connectedVertices[1]]; 283 | 284 | if (vertex0 == -1 || vertex1 == -1) 285 | { 286 | continue; 287 | } 288 | 289 | vector sharedEdges = intersection( 290 | meshData.vertexData[vertex0].connectedEdges, 291 | meshData.vertexData[vertex1].connectedEdges 292 | ); 293 | 294 | if (sharedEdges.size() != 1) { continue; } 295 | 296 | edgeIndex = sharedEdges[0]; 297 | 298 | if (!examinedEdges[edgeIndex]) 299 | { 300 | symmetricalEdgesQueue.push(pair(e, edgeIndex)); 301 | } 302 | 303 | markSymmetricalEdges(e, edgeIndex); 304 | 305 | } 306 | } 307 | 308 | 309 | int PolySymmetryData::getUnexaminedVertexSibling(int &vertexIndex, int &faceIndex) 310 | { 311 | int result = -1; 312 | 313 | for (int &v : meshData.vertexData[vertexIndex].faceVertexSiblings[faceIndex]) 314 | { 315 | if (examinedVertices[v]) 316 | { 317 | continue; 318 | } 319 | 320 | result = v; 321 | break; 322 | } 323 | 324 | return result; 325 | } 326 | 327 | 328 | void PolySymmetryData::findVertexSides(vector &leftSideVertexIndices) 329 | { 330 | int LEFT = 1; 331 | int RIGHT = -1; 332 | int CENTER = 0; 333 | 334 | vector visitedVertices(meshData.numberOfVertices, false); 335 | queue nextVertexQueue = queue(); 336 | 337 | vertexSides.clear(); 338 | vertexSides.resize(meshData.numberOfVertices, 0); 339 | 340 | for (int &i : leftSideVertexIndices) 341 | { 342 | nextVertexQueue.push(i); 343 | vertexSides[i] = LEFT; 344 | } 345 | 346 | int vertexIndex = -1; 347 | 348 | while (!nextVertexQueue.empty()) 349 | { 350 | vertexIndex = nextVertexQueue.front(); 351 | nextVertexQueue.pop(); 352 | 353 | if (visitedVertices[vertexIndex]) { continue; } 354 | 355 | visitedVertices[vertexIndex] = true; 356 | 357 | if (vertexSymmetryIndices[vertexIndex] == vertexIndex) 358 | { 359 | vertexSides[vertexIndex] = CENTER; 360 | } else { 361 | vertexSides[vertexIndex] = LEFT; 362 | 363 | for (int &i : meshData.vertexData[vertexIndex].connectedVertices) 364 | { 365 | if (!visitedVertices[i] && vertexSymmetryIndices[i] != vertexIndex) 366 | { 367 | nextVertexQueue.push(i); 368 | } 369 | } 370 | } 371 | } 372 | 373 | for (int &i : leftSideVertexIndices) 374 | { 375 | nextVertexQueue.push(vertexSymmetryIndices[i]); 376 | vertexSides[vertexSymmetryIndices[i]] = RIGHT; 377 | } 378 | 379 | while (!nextVertexQueue.empty()) 380 | { 381 | vertexIndex = nextVertexQueue.front(); 382 | nextVertexQueue.pop(); 383 | 384 | if (visitedVertices[vertexIndex]) { continue; } 385 | 386 | visitedVertices[vertexIndex] = true; 387 | 388 | if (vertexSymmetryIndices[vertexIndex] == vertexIndex) 389 | { 390 | vertexSides[vertexIndex] = CENTER; 391 | } else { 392 | vertexSides[vertexIndex] = RIGHT; 393 | 394 | for (int &i : meshData.vertexData[vertexIndex].connectedVertices) 395 | { 396 | if (!visitedVertices[i]) 397 | { 398 | nextVertexQueue.push(i); 399 | } 400 | } 401 | } 402 | } 403 | } 404 | 405 | 406 | void PolySymmetryData::markSymmetricalVertices(int &i0, int &i1) 407 | { 408 | vertexSymmetryIndices[i0] = i1; 409 | vertexSymmetryIndices[i1] = i0; 410 | 411 | examinedVertices[i0] = true; 412 | examinedVertices[i1] = true; 413 | } 414 | 415 | 416 | void PolySymmetryData::markSymmetricalEdges(int &i0, int &i1) 417 | { 418 | edgeSymmetryIndices[i0] = i1; 419 | edgeSymmetryIndices[i1] = i0; 420 | 421 | examinedEdges[i0] = true; 422 | examinedEdges[i1] = true; 423 | } 424 | 425 | 426 | void PolySymmetryData::markSymmetricalFaces(int &i0, int &i1) 427 | { 428 | faceSymmetryIndices[i0] = i1; 429 | faceSymmetryIndices[i1] = i0; 430 | 431 | examinedFaces[i0] = true; 432 | examinedFaces[i1] = true; 433 | } 434 | 435 | 436 | void PolySymmetryData::finalizeSymmetry() 437 | { 438 | int LEFT = 1; 439 | int RIGHT = -1; 440 | int CENTER = 0; 441 | 442 | for (int i = 0; i < meshData.numberOfEdges; i++) 443 | { 444 | int sv0 = vertexSides[meshData.edgeData[i].connectedVertices[0]]; 445 | int sv1 = vertexSides[meshData.edgeData[i].connectedVertices[1]]; 446 | 447 | if (sv0 == CENTER && sv1 == CENTER) 448 | { 449 | edgeSides[i] = CENTER; 450 | } else if (sv0 == RIGHT || sv1 == RIGHT) { 451 | edgeSides[i] = RIGHT; 452 | } else if (sv0 == LEFT || sv1 == LEFT) { 453 | edgeSides[i] = LEFT; 454 | } 455 | } 456 | 457 | vector faceVertexSides; 458 | 459 | for (int i = 0; i < meshData.numberOfFaces; i++) 460 | { 461 | faceVertexSides.resize(meshData.faceData[i].connectedVertices.size()); 462 | 463 | for (int j = 0; j < meshData.faceData[i].connectedVertices.size(); j++) 464 | { 465 | faceVertexSides[j] = vertexSides[meshData.faceData[i].connectedVertices[j]]; 466 | } 467 | 468 | bool onTheLeft = contains(faceVertexSides, LEFT); 469 | bool onTheRight = contains(faceVertexSides, RIGHT); 470 | 471 | faceSides[i] = (onTheLeft ? LEFT : CENTER) + (onTheRight ? RIGHT : CENTER); 472 | } 473 | } -------------------------------------------------------------------------------- /polySymmetry/scripts/polySymmetry/commands.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | 3 | import maya.api.OpenMaya as OpenMaya 4 | import maya.cmds as cmds 5 | import maya.mel as mel 6 | 7 | import polySymmetry.utils 8 | 9 | 10 | _INFO = OpenMaya.MGlobal.displayInfo 11 | _ERR = OpenMaya.MGlobal.displayError 12 | _WARN = OpenMaya.MGlobal.displayWarning 13 | 14 | 15 | def copyPolyDeformerWeights(*args): 16 | """Copies the weights from one deformer to another. 17 | 18 | Parameters 19 | ---------- 20 | *args : str 21 | List of one or two meshes, and two deformers. If empty, the active 22 | selection is used. 23 | 24 | Raises 25 | ------ 26 | RuntimeError 27 | If the selected deformers do not deform the selected mesh(es), or if 28 | the selected mesh(es) do not have poly symmetry data computed. 29 | 30 | """ 31 | 32 | selectedMeshes = _getSelectedMeshes(*args) 33 | selectedDeformers = _getSelectedDeformers(*args) 34 | 35 | if len(selectedMeshes) not in (1, 2) or len(selectedDeformers) != 2: 36 | _ERR( 37 | "Must select a source mesh, a destination mesh (optional), " 38 | "a source deformer, and a destination deformer." 39 | ) 40 | return 41 | 42 | kwargs = { 43 | 'sourceMesh': selectedMeshes[0], 44 | 'sourceDeformer': selectedDeformers[0], 45 | 'destinationMesh': selectedMeshes[-1], 46 | 'destinationDeformer': selectedDeformers[1], 47 | 'direction': 1 48 | } 49 | 50 | cmds.polyDeformerWeights(**kwargs) 51 | 52 | _INFO( 53 | "Result: Copied weights from {sourceDeformer} ({sourceMesh}) " 54 | "to {destinationDeformer} ({destinationMesh})".format(**kwargs) 55 | ) 56 | 57 | 58 | def flipDeformerWeights(*args): 59 | """Flips the weights on the selected deformer(s). 60 | 61 | Parameters 62 | ---------- 63 | *args : str 64 | List of equal number of meshes and deformers. If empty, the active 65 | selection is used. 66 | 67 | Raises 68 | ------ 69 | RuntimeError 70 | If the selected deformers do not deform the selected meshes, or if 71 | the selected meshes do not have poly symmetry data computed. 72 | 73 | """ 74 | 75 | polyDeformerWeights(*args, flip=True) 76 | 77 | 78 | def mirrorDeformerWeights(*args): 79 | """Mirrors the weights on the selected deformer(s). 80 | 81 | Parameters 82 | ---------- 83 | *args : str 84 | List of equal number of meshes and deformers. If empty, the active 85 | selection is used. 86 | 87 | Raises 88 | ------ 89 | RuntimeError 90 | If the selected deformers do not deform the selected meshes, or if 91 | the selected meshes do not have poly symmetry data computed. 92 | 93 | """ 94 | 95 | polyDeformerWeights(*args, mirror=True) 96 | 97 | 98 | def polyDeformerWeights(*args, **kwargs): 99 | """Copies the weights from one deformer to another. 100 | 101 | Parameters 102 | ---------- 103 | *args : str 104 | List of equal number of meshes and deformers. If empty, the active 105 | selection is used. 106 | 107 | Raises 108 | ------ 109 | RuntimeError 110 | If the selected deformers do not deform the selected meshes, or if 111 | the selected meshes do not have poly symmetry data computed. 112 | 113 | """ 114 | 115 | selectedMeshes = _getSelectedMeshes(*args) 116 | selectedDeformers = _getSelectedDeformers(*args) 117 | 118 | if not selectedMeshes or not selectedDeformers: 119 | raise polySymmetry.utils.InvalidSelection("Must select at least one mesh and one deformer.") 120 | return 121 | 122 | if len(selectedMeshes) != len(selectedDeformers): 123 | raise polySymmetry.utils.InvalidSelection("Must select exactly one deformer per mesh.") 124 | return 125 | 126 | logMsg = "Result: {} weights on {} ({})." 127 | actionName = 'Flipped' if 'flip' in kwargs else 'Mirrored' 128 | 129 | with undoChunk(): 130 | for mesh, deformer in zip(selectedMeshes, selectedDeformers): 131 | cmds.polyDeformerWeights( 132 | sourceMesh=mesh, 133 | sourceDeformer=deformer, 134 | destinationMesh=mesh, 135 | destinationDeformer=deformer, 136 | **kwargs 137 | ) 138 | 139 | _INFO(logMsg.format(actionName, mesh, deformer)) 140 | 141 | 142 | def flipMesh(*args): 143 | """Flips the vertex positions of the selected mesh(es). 144 | 145 | Parameters 146 | ---------- 147 | *args : str 148 | List of meshes. If empty, the active selection is used. 149 | 150 | Raises 151 | ------ 152 | RuntimeError 153 | If the selected mesh(es) do not have poly symmetry data computed. 154 | 155 | """ 156 | 157 | selectedMeshes = _getSelectedMeshes(*args) 158 | 159 | if not selectedMeshes: 160 | raise polySymmetry.utils.InvalidSelection("Select a mesh and try again.") 161 | return 162 | 163 | with undoChunk(): 164 | for mesh in selectedMeshes: 165 | cmds.polyFlip(mesh, objectSpace=True) 166 | 167 | 168 | def mirrorMesh(*args): 169 | """Mirrors the vertex positions of the selected mesh(es). 170 | 171 | Parameters 172 | ---------- 173 | *args : str 174 | A base mesh and one or more target meshes. If empty, the active 175 | selection is used. 176 | 177 | Raises 178 | ------ 179 | RuntimeError 180 | If the base mesh and the target mesh(es) are not point compatible, 181 | or if the selected mesh(es) do not have poly symmetry data computed. 182 | 183 | """ 184 | 185 | selectedMeshes = _getSelectedMeshes(*args) 186 | 187 | if len(selectedMeshes) < 2: 188 | raise polySymmetry.utils.InvalidSelection("Select a base mesh and a target mesh and try again.") 189 | return 190 | 191 | baseMesh = selectedMeshes[0] 192 | 193 | with undoChunk(): 194 | for mesh in selectedMeshes[1:]: 195 | cmds.polyMirror(baseMesh, mesh) 196 | 197 | 198 | def copyPolySkinWeights(*args): 199 | """Copies the skinCluster weights from one mesh to another. 200 | 201 | Parameters 202 | ---------- 203 | *args : str 204 | Two skinned polygon meshes. If empty, the active selection is used. 205 | 206 | Raises 207 | ------ 208 | RuntimeError 209 | If the source mesh and target mesh are not point compatible. 210 | 211 | """ 212 | 213 | try: 214 | sourceMesh, destinationMesh = _getSelectedMeshes(*args) 215 | except ValueError: 216 | raise polySymmetry.utils.InvalidSelection("Must select a souce mesh and a destination mesh.") 217 | return 218 | 219 | sourceSkin = _getSkinCluster(sourceMesh) 220 | destinationSkin = _getSkinCluster(destinationMesh) 221 | 222 | if not sourceSkin: 223 | raise polySymmetry.utils.InvalidSelection("'%s' is not skinned." % sourceMesh) 224 | return 225 | 226 | if not destinationSkin: 227 | raise polySymmetry.utils.InvalidSelection("'%s' is not skinned." % destinationMesh) 228 | return 229 | 230 | cmds.polySkinWeights( 231 | sourceMesh=sourceMesh, 232 | sourceSkin=sourceSkin, 233 | destinationMesh=destinationMesh, 234 | destinationSkin=destinationSkin, 235 | direction=1, 236 | normalize=True 237 | ) 238 | 239 | 240 | def mirrorPolySkinWeights(*args, **kwargs): 241 | """Mirrors the skinCluster weights on the selected mesh(es). 242 | 243 | Parameters 244 | ---------- 245 | *args : str 246 | List of skinned polygon meshes. If empty, the active selection is used. 247 | 248 | Raises 249 | ------ 250 | RuntimeError 251 | If the selected mesh(es) do not have poly symmetry data computed. 252 | 253 | """ 254 | 255 | activeSelection = OpenMaya.MGlobal.getActiveSelectionList() 256 | numberOfSelectedItems = activeSelection.length() 257 | 258 | selectedMeshes = _getSelectedMeshes(*args) 259 | skinClusters = [_getSkinCluster(mesh) for mesh in selectedMeshes] 260 | 261 | if not selectedMeshes: 262 | raise polySymmetry.utils.InvalidSelection("Select a skinned mesh and try again.") 263 | 264 | if not kwargs: 265 | options = polySymmetry.utils.loadOptions('polySkinWeights') 266 | 267 | kwargs = {'mirror': True} 268 | 269 | if options.get('normalize', False): 270 | kwargs['normalize'] = True 271 | 272 | if options.get('useInfluencePattern', True): 273 | kwargs['influenceSymmetry'] = ( 274 | options.get('leftPattern', 'L_*'), 275 | options.get('rightPattern', 'R_*') 276 | ) 277 | 278 | kwargs['direction'] = 1 if options.get('direction', 1) == 1 else -1 279 | 280 | with undoChunk(): 281 | for mesh, skin in zip(selectedMeshes, skinClusters): 282 | 283 | if not skin: 284 | _WARN("Skipping '%s' since it is not skinned." % mesh) 285 | continue 286 | 287 | cmds.polySkinWeights( 288 | sourceMesh=mesh, 289 | sourceSkin=skin, 290 | destinationMesh=mesh, 291 | destinationSkin=skin, 292 | **kwargs 293 | ) 294 | 295 | 296 | def setInfluenceSymmetry(*args, **kwargs): 297 | """Sets the symmetry attributes of the influences for the selected mesh(es). 298 | 299 | Parameters 300 | ---------- 301 | *args : str 302 | List of skinned polygon meshes. If empty, the active selection is used. 303 | **leftPattern : str 304 | Pattern for matching influences on the left side of the mesh. 305 | **rightPattern : str 306 | Pattern for matching influences on the right side of the mesh. 307 | """ 308 | 309 | selectedMeshes = _getSelectedMeshes(*args) 310 | 311 | leftPattern = kwargs.get('leftPattern', 'L_*') 312 | rightPattern = kwargs.get('leftPattern', 'R_*') 313 | 314 | if not selectedMeshes: 315 | raise polySymmetry.utils.InvalidSelection("Select a skinned mesh and try again.") 316 | 317 | with undoChunk(): 318 | for mesh in selectedMeshes: 319 | skin = _getSkinCluster(mesh) 320 | 321 | if not skin: 322 | _WARN("Skipping '%s' since it is not skinned." % mesh) 323 | continue 324 | 325 | cmds.polySkinWeights( 326 | skin, 327 | edit=True, 328 | influenceSymmetry=(leftPattern, rightPattern) 329 | ) 330 | 331 | 332 | def printInfluenceSymmetry(*args): 333 | """Sets the symmetry attributes of the influences for the selected mesh(es). 334 | 335 | Parameters 336 | ---------- 337 | *args : str 338 | List of skinned polygon meshes. If empty, the active selection is used. 339 | 340 | """ 341 | 342 | selectedMeshes = _getSelectedMeshes(*args) 343 | 344 | if not selectedMeshes: 345 | raise polySymmetry.utils.InvalidSelection("Select a skinned mesh and try again.") 346 | 347 | with undoChunk(): 348 | for mesh in selectedMeshes: 349 | skin = _getSkinCluster(mesh) 350 | 351 | if not skin: 352 | _WARN("Skipping '%s' since it is not skinned." % mesh) 353 | continue 354 | 355 | result = cmds.polySkinWeights( 356 | skin, 357 | query=True, 358 | influenceSymmetry=True 359 | ) 360 | 361 | print('Mesh: {}'.format(mesh)) 362 | print('Skin: {}'.format(skin)) 363 | print('\n'.join(['{:>32} : {:>32}'.format(*sym.split(':')) for sym in result])) 364 | 365 | 366 | def _getSkinCluster(mesh): 367 | """Returns the skinCluster that deforms `mesh`. 368 | 369 | Parameters 370 | ---------- 371 | mesh : str 372 | Name of a polygon mesh. 373 | 374 | Returns 375 | ------- 376 | str 377 | Name of a skinCluster. 378 | 379 | """ 380 | 381 | return mel.eval('findRelatedSkinCluster "%s"' % mesh) 382 | 383 | 384 | def _getSelection(*args): 385 | """Returns the user specified selection, or the scene selection. 386 | 387 | Parameters 388 | ---------- 389 | *args : str 390 | User specified selection. If empty, the active selection is used. 391 | 392 | Returns 393 | ------- 394 | list 395 | List of selected nodes. 396 | 397 | """ 398 | 399 | if args: 400 | selection = OpenMaya.MSelectionList() 401 | 402 | for arg in args: 403 | try: 404 | selection.add(arg) 405 | except RuntimeError: 406 | raise NameError("No object matches name '%s'" % arg) 407 | else: 408 | selection = OpenMaya.MGlobal.getActiveSelectionList() 409 | 410 | return selection 411 | 412 | 413 | def _getSelectedNodes(fnType, *args): 414 | """Returns the selected nodes of type `fnType`. 415 | 416 | Parameters 417 | ---------- 418 | fnType : OpenMaya.MFn 419 | Node type enum. Only nodes of this type will be returned. 420 | *args : str 421 | User specified selection. If empty, the active selection is used. 422 | 423 | Returns 424 | ------- 425 | list 426 | List of selected nodes. 427 | 428 | """ 429 | 430 | selection = _getSelection(*args) 431 | numberOfSelectedItems = selection.length() 432 | 433 | result = [] 434 | 435 | for i in range(numberOfSelectedItems): 436 | obj = selection.getDependNode(i) 437 | 438 | if obj.hasFn(fnType): 439 | result.append(OpenMaya.MFnDependencyNode(obj).name()) 440 | 441 | return result 442 | 443 | 444 | def _getSelectedDeformers(*args): 445 | """Returns the selected deformers. 446 | 447 | Parameters 448 | ---------- 449 | *args : str 450 | User specified selection. If empty, the active selection is used. 451 | 452 | Returns 453 | ------- 454 | list 455 | List of selected deformers. 456 | 457 | """ 458 | 459 | return _getSelectedNodes(OpenMaya.MFn.kWeightGeometryFilt, *args) 460 | 461 | 462 | def _getSelectedMeshes(*args): 463 | """Returns the selected meshes. 464 | 465 | Parameters 466 | ---------- 467 | *args : str 468 | User specified selection. If empty, the active selection is used. 469 | 470 | Returns 471 | ------- 472 | list 473 | List of selected meshes. 474 | 475 | """ 476 | 477 | selection = _getSelection(*args) 478 | numberOfSelectedItems = selection.length() 479 | 480 | result = [] 481 | 482 | for i in range(numberOfSelectedItems): 483 | try: 484 | dagPath = selection.getDagPath(i) 485 | except TypeError: 486 | continue 487 | 488 | if dagPath.hasFn(OpenMaya.MFn.kMesh): 489 | result.append(dagPath.partialPathName()) 490 | 491 | return result 492 | 493 | 494 | @contextlib.contextmanager 495 | def undoChunk(): 496 | """Wraps a block of code in an undo chunk.""" 497 | 498 | try: 499 | cmds.undoInfo(openChunk=True) 500 | yield 501 | except: 502 | raise 503 | finally: 504 | cmds.undoInfo(closeChunk=True) 505 | -------------------------------------------------------------------------------- /src/polySymmetryCmd.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include "meshData.h" 7 | #include "polySymmetryCmd.h" 8 | #include "polySymmetryNode.h" 9 | #include "sceneCache.h" 10 | #include "selection.h" 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | using namespace std; 27 | 28 | #define RETURN_IF_ERROR(s) if (!s) { return s; } 29 | 30 | PolySymmetryCommand::PolySymmetryCommand() 31 | { 32 | meshSymmetryNode = MObject(); 33 | 34 | meshData = MeshData(); 35 | meshSymmetryData = PolySymmetryData(); 36 | 37 | symmetryComponents = vector(); 38 | leftSideVertexIndices = vector(); 39 | } 40 | 41 | 42 | PolySymmetryCommand::~PolySymmetryCommand() 43 | { 44 | meshData.clear(); 45 | symmetryComponents.clear(); 46 | leftSideVertexIndices.clear(); 47 | } 48 | 49 | 50 | void* PolySymmetryCommand::creator() 51 | { 52 | return new PolySymmetryCommand(); 53 | } 54 | 55 | 56 | MSyntax PolySymmetryCommand::getSyntax() 57 | { 58 | MSyntax syntax; 59 | 60 | syntax.useSelectionAsDefault(true); 61 | syntax.setObjectType(MSyntax::kSelectionList, 1, 1); 62 | syntax.enableQuery(true); 63 | 64 | syntax.addFlag( 65 | SYMMETRY_COMPONENTS_FLAG, 66 | SYMMETRY_COMPONENTS_LONG_FLAG, 67 | MSyntax::MArgType::kString, 68 | MSyntax::MArgType::kString, 69 | MSyntax::MArgType::kString, 70 | MSyntax::MArgType::kString, 71 | MSyntax::MArgType::kString, 72 | MSyntax::MArgType::kString 73 | ); 74 | 75 | syntax.addFlag( 76 | LEFT_SIDE_VERTEX_FLAG, 77 | LEFT_SIDE_VERTEX_LONG_FLAG, 78 | MSyntax::MArgType::kString 79 | ); 80 | 81 | syntax.addFlag( 82 | CONSTRUCTION_HISTORY_FLAG, 83 | CONSTRUCTION_HISTORY_LONG_FLAG, 84 | MSyntax::MArgType::kBoolean 85 | ); 86 | 87 | syntax.addFlag( 88 | EXISTS_FLAG, 89 | EXISTS_LONG_FLAG, 90 | MSyntax::MArgType::kBoolean 91 | ); 92 | 93 | syntax.makeFlagMultiUse(SYMMETRY_COMPONENTS_FLAG); 94 | syntax.makeFlagMultiUse(LEFT_SIDE_VERTEX_FLAG); 95 | 96 | return syntax; 97 | } 98 | 99 | 100 | MStatus PolySymmetryCommand::doIt(const MArgList& argList) 101 | { 102 | MStatus status; 103 | 104 | MArgDatabase argsData(syntax(), argList, &status); 105 | CHECK_MSTATUS_AND_RETURN_IT(status); 106 | 107 | if (argsData.isQuery()) 108 | { 109 | status = this->parseQueryArguments(argsData); 110 | RETURN_IF_ERROR(status); 111 | } else { 112 | status = this->parseArguments(argsData); 113 | RETURN_IF_ERROR(status); 114 | 115 | meshSymmetryData.initialize(selectedMesh); 116 | } 117 | 118 | return this->redoIt(); 119 | } 120 | 121 | 122 | MStatus PolySymmetryCommand::redoIt() 123 | { 124 | MStatus status; 125 | 126 | if (this->isQuery) 127 | { 128 | if (this->selectedMesh.isValid()) 129 | { 130 | status = this->doQueryMeshAction(); 131 | } else { 132 | status = this->doQueryDataAction(); 133 | } 134 | } else { 135 | status = this->doUndoableCommand(); 136 | } 137 | 138 | return status; 139 | } 140 | 141 | 142 | MStatus PolySymmetryCommand::doQueryDataAction() 143 | { 144 | MStatus status = this->getSymmetricalComponentsFromNode(); 145 | CHECK_MSTATUS_AND_RETURN_IT(status); 146 | 147 | this->createResultString(); 148 | 149 | return MStatus::kSuccess; 150 | } 151 | 152 | 153 | MStatus PolySymmetryCommand::doQueryMeshAction() 154 | { 155 | bool cacheHit = PolySymmetryCache::getNodeFromCache(this->selectedMesh, this->meshSymmetryNode); 156 | 157 | if (this->isQueryExists) 158 | { 159 | this->setResult(cacheHit); 160 | } else { 161 | if (cacheHit) 162 | { 163 | MString resultName = MFnDependencyNode(this->meshSymmetryNode).name(); 164 | this->setResult(resultName); 165 | } else { 166 | MString warningMsg("No polySymmetryData node in memory matches the mesh ^1s."); 167 | warningMsg.format(warningMsg, this->selectedMesh.partialPathName()); 168 | 169 | MGlobal::displayWarning(warningMsg); 170 | } 171 | } 172 | 173 | return MStatus::kSuccess; 174 | } 175 | 176 | 177 | MStatus PolySymmetryCommand::doUndoableCommand() 178 | { 179 | MStatus status; 180 | 181 | status = this->getSymmetricalComponentsFromScene(); 182 | CHECK_MSTATUS_AND_RETURN_IT(status); 183 | 184 | if (constructionHistory) 185 | { 186 | status = this->createResultNode(); 187 | CHECK_MSTATUS_AND_RETURN_IT(status); 188 | 189 | } else { 190 | this->createResultString(); 191 | } 192 | 193 | return MStatus::kSuccess; 194 | } 195 | 196 | 197 | MStatus PolySymmetryCommand::undoIt() 198 | { 199 | MStatus status; 200 | 201 | MDGModifier dgModifier; 202 | 203 | dgModifier.deleteNode(this->meshSymmetryNode); 204 | 205 | status = dgModifier.doIt(); 206 | CHECK_MSTATUS_AND_RETURN_IT(status); 207 | 208 | return MStatus::kSuccess; 209 | } 210 | 211 | 212 | const bool PolySymmetryCommand::isUndoable() 213 | { 214 | return !this->meshSymmetryNode.isNull(); 215 | } 216 | 217 | 218 | MStatus PolySymmetryCommand::parseQueryArguments(MArgDatabase &argsData) 219 | { 220 | MStatus status; 221 | 222 | this->isQuery = true; 223 | this->isQueryExists = argsData.isFlagSet(EXISTS_FLAG); 224 | 225 | MSelectionList selection; 226 | status = argsData.getObjects(selection); 227 | CHECK_MSTATUS_AND_RETURN_IT(status); 228 | 229 | MObject obj; 230 | status = selection.getDependNode(0, obj); 231 | CHECK_MSTATUS_AND_RETURN_IT(status); 232 | 233 | MString objectType = MFnDependencyNode(obj).typeName(); 234 | 235 | if (objectType == PolySymmetryNode::NODE_NAME) 236 | { 237 | this->meshSymmetryNode = obj; 238 | return MStatus::kSuccess; 239 | } 240 | 241 | MDagPath dagPath; 242 | status = selection.getDagPath(0, dagPath); 243 | 244 | if (status && dagPath.hasFn(MFn::kMesh)) 245 | { 246 | this->selectedMesh.set(dagPath); 247 | return MStatus::kSuccess; 248 | } 249 | 250 | MString errorMsg("polySymmetry command requires a mesh or ^1s in query node, not a(n) ^2s"); 251 | errorMsg.format(errorMsg, PolySymmetryNode::NODE_NAME, objectType); 252 | MGlobal::displayError(errorMsg); 253 | 254 | return MStatus::kFailure; 255 | } 256 | 257 | 258 | MStatus PolySymmetryCommand::parseArguments(MArgDatabase &argsData) 259 | { 260 | MStatus status; 261 | 262 | bool constructionHistoryFlagSet = argsData.isFlagSet(CONSTRUCTION_HISTORY_FLAG, &status); 263 | CHECK_MSTATUS_AND_RETURN_IT(status); 264 | 265 | if (constructionHistoryFlagSet) 266 | { 267 | status = argsData.getFlagArgument(CONSTRUCTION_HISTORY_FLAG, 0, this->constructionHistory); 268 | CHECK_MSTATUS_AND_RETURN_IT(status); 269 | } else { 270 | constructionHistory = true; 271 | } 272 | 273 | status = this->getSelectedMesh(argsData); 274 | RETURN_IF_ERROR(status); 275 | 276 | status = this->getSymmetryComponents(argsData); 277 | RETURN_IF_ERROR(status); 278 | 279 | status = this->getLeftSideVertexIndices(argsData); 280 | RETURN_IF_ERROR(status); 281 | 282 | return MStatus::kSuccess; 283 | } 284 | 285 | 286 | MStatus PolySymmetryCommand::getSelectedMesh(MArgDatabase &argsData) 287 | { 288 | MStatus status; 289 | MSelectionList selection; 290 | 291 | status = argsData.getObjects(selection); 292 | CHECK_MSTATUS_AND_RETURN_IT(status); 293 | 294 | status = selection.getDagPath(0, selectedMesh); 295 | 296 | if (!status || !selectedMesh.hasFn(MFn::Type::kMesh)) 297 | { 298 | MGlobal::displayError("Must select a mesh."); 299 | return MStatus::kFailure; 300 | } 301 | 302 | meshData.unpackMesh(selectedMesh); 303 | 304 | if (selectedMesh.node().hasFn(MFn::kMesh)) 305 | { 306 | selectedMesh.pop(); 307 | } 308 | 309 | return MStatus::kSuccess; 310 | } 311 | 312 | 313 | MStatus PolySymmetryCommand::getSymmetryComponents(MArgDatabase &argsData) 314 | { 315 | MStatus status; 316 | 317 | uint numberOfSymmetricalComponentLists = argsData.numberOfFlagUses(SYMMETRY_COMPONENTS_FLAG); 318 | 319 | for (uint i = 0; i < numberOfSymmetricalComponentLists; i++) 320 | { 321 | MArgList args; 322 | MSelectionList selection; 323 | 324 | status = argsData.getFlagArgumentList(SYMMETRY_COMPONENTS_FLAG, i, args); 325 | CHECK_MSTATUS_AND_RETURN_IT(status); 326 | 327 | status = this->getFlagStringArguments(args, selection); 328 | CHECK_MSTATUS_AND_RETURN_IT(status); 329 | 330 | ComponentSelection scs; 331 | MSelectionList filteredSelection; 332 | 333 | getSelectedComponents(selectedMesh, selection, filteredSelection, MFn::Type::kMeshEdgeComponent); 334 | getSelectedComponents(selectedMesh, selection, filteredSelection, MFn::Type::kMeshVertComponent); 335 | getSelectedComponents(selectedMesh, selection, filteredSelection, MFn::Type::kMeshPolygonComponent); 336 | 337 | bool isValidSelection = getSymmetricalComponentSelection(meshData, filteredSelection, scs, false); 338 | 339 | if (isValidSelection) 340 | { 341 | symmetryComponents.push_back(scs); 342 | } else { 343 | MGlobal::displayError("Must select a symmetrical edge, face, and vertex on both sides of the mesh."); 344 | return MStatus::kFailure; 345 | } 346 | } 347 | 348 | if (symmetryComponents.empty()) 349 | { 350 | MGlobal::displayError("Must specify at least one pair of symmetrical components."); 351 | return MStatus::kFailure; 352 | } 353 | 354 | return MStatus::kSuccess; 355 | } 356 | 357 | 358 | void PolySymmetryCommand::setSymmetryComponents(vector &components) 359 | { 360 | for (ComponentSelection &cs : components) 361 | { 362 | this->symmetryComponents.push_back(cs); 363 | } 364 | } 365 | 366 | 367 | MStatus PolySymmetryCommand::getLeftSideVertexIndices(MArgDatabase &argsData) 368 | { 369 | MStatus status; 370 | 371 | MSelectionList selection; 372 | uint numberOfFlagUses = argsData.numberOfFlagUses(LEFT_SIDE_VERTEX_FLAG); 373 | 374 | for (uint i = 0; i < numberOfFlagUses; i++) 375 | { 376 | MArgList args; 377 | status = argsData.getFlagArgumentList(LEFT_SIDE_VERTEX_FLAG, i, args); 378 | CHECK_MSTATUS_AND_RETURN_IT(status); 379 | 380 | status = this->getFlagStringArguments(args, selection); 381 | } 382 | 383 | getSelectedComponentIndices(selection, leftSideVertexIndices, MFn::kMeshVertComponent); 384 | 385 | if (leftSideVertexIndices.empty()) 386 | { 387 | MGlobal::displayError("Must specify at least one vertex on the left side of the mesh."); 388 | return MStatus::kFailure; 389 | } 390 | 391 | return MStatus::kSuccess; 392 | } 393 | 394 | 395 | void PolySymmetryCommand::setLeftSideVertexIndices(vector &indices) 396 | { 397 | for (int &i : indices) 398 | { 399 | this->leftSideVertexIndices.push_back(i); 400 | } 401 | } 402 | 403 | 404 | MStatus PolySymmetryCommand::getFlagStringArguments(MArgList &args, MSelectionList &selection) 405 | { 406 | MStatus status; 407 | MString mesh = this->selectedMesh.partialPathName(); 408 | 409 | for (uint argIndex = 0; argIndex < args.length(); argIndex++) 410 | { 411 | MString obj = args.asString(argIndex, &status); 412 | CHECK_MSTATUS_AND_RETURN_IT(status); 413 | 414 | if (obj.index('.') == -1) 415 | { 416 | obj = mesh + "." + obj; 417 | } 418 | 419 | status = selection.add(obj); 420 | 421 | if (!status) 422 | { 423 | MGlobal::displayError("No object matches name: " + obj); 424 | return status; 425 | } 426 | } 427 | 428 | return MStatus::kSuccess; 429 | } 430 | 431 | 432 | MStatus PolySymmetryCommand::getSymmetricalComponentsFromNode() 433 | { 434 | MStatus status; 435 | MFnDependencyNode fnNode(this->meshSymmetryNode); 436 | 437 | status = PolySymmetryNode::getValues(fnNode, EDGE_SYMMETRY, this->meshSymmetryData.edgeSymmetryIndices); 438 | CHECK_MSTATUS_AND_RETURN_IT(status); 439 | 440 | status = PolySymmetryNode::getValues(fnNode, FACE_SYMMETRY, this->meshSymmetryData.faceSymmetryIndices); 441 | CHECK_MSTATUS_AND_RETURN_IT(status); 442 | 443 | status = PolySymmetryNode::getValues(fnNode, VERTEX_SYMMETRY, this->meshSymmetryData.vertexSymmetryIndices); 444 | CHECK_MSTATUS_AND_RETURN_IT(status); 445 | 446 | status = PolySymmetryNode::getValues(fnNode, EDGE_SIDES, this->meshSymmetryData.edgeSides); 447 | CHECK_MSTATUS_AND_RETURN_IT(status); 448 | 449 | status = PolySymmetryNode::getValues(fnNode, FACE_SIDES, this->meshSymmetryData.faceSides); 450 | CHECK_MSTATUS_AND_RETURN_IT(status); 451 | 452 | status = PolySymmetryNode::getValues(fnNode, VERTEX_SIDES, this->meshSymmetryData.vertexSides); 453 | CHECK_MSTATUS_AND_RETURN_IT(status); 454 | 455 | return MStatus::kSuccess; 456 | } 457 | 458 | 459 | MStatus PolySymmetryCommand::getSymmetricalComponentsFromScene() 460 | { 461 | MStatus status; 462 | 463 | for (ComponentSelection &s : symmetryComponents) 464 | { 465 | this->meshSymmetryData.findSymmetricalVertices(s); 466 | } 467 | 468 | this->meshSymmetryData.findVertexSides(leftSideVertexIndices); 469 | this->meshSymmetryData.finalizeSymmetry(); 470 | 471 | return MStatus::kSuccess; 472 | } 473 | 474 | 475 | MStatus PolySymmetryCommand::createResultNode() 476 | { 477 | MDGModifier dgModifier; 478 | 479 | MStatus status; 480 | meshSymmetryNode = dgModifier.createNode(PolySymmetryNode::NODE_NAME, &status); 481 | CHECK_MSTATUS_AND_RETURN_IT(status); 482 | 483 | PolySymmetryCache::cacheNodes = false; 484 | status = dgModifier.doIt(); 485 | PolySymmetryCache::cacheNodes = true; 486 | 487 | CHECK_MSTATUS_AND_RETURN_IT(status); 488 | 489 | MDGModifier renameModifier; 490 | status = renameModifier.renameNode(meshSymmetryNode, selectedMesh.partialPathName() + "Symmetry"); 491 | CHECK_MSTATUS_AND_RETURN_IT(status); 492 | 493 | MFnDependencyNode fnNode(meshSymmetryNode); 494 | 495 | status = PolySymmetryNode::setValues(fnNode, EDGE_SYMMETRY, this->meshSymmetryData.edgeSymmetryIndices); 496 | CHECK_MSTATUS_AND_RETURN_IT(status); 497 | 498 | status = PolySymmetryNode::setValues(fnNode, FACE_SYMMETRY, this->meshSymmetryData.faceSymmetryIndices); 499 | CHECK_MSTATUS_AND_RETURN_IT(status); 500 | 501 | status = PolySymmetryNode::setValues(fnNode, VERTEX_SYMMETRY, this->meshSymmetryData.vertexSymmetryIndices); 502 | CHECK_MSTATUS_AND_RETURN_IT(status); 503 | 504 | status = PolySymmetryNode::setValues(fnNode, EDGE_SIDES, this->meshSymmetryData.edgeSides); 505 | CHECK_MSTATUS_AND_RETURN_IT(status); 506 | 507 | status = PolySymmetryNode::setValues(fnNode, FACE_SIDES, this->meshSymmetryData.faceSides); 508 | CHECK_MSTATUS_AND_RETURN_IT(status); 509 | 510 | status = PolySymmetryNode::setValues(fnNode, VERTEX_SIDES, this->meshSymmetryData.vertexSides); 511 | CHECK_MSTATUS_AND_RETURN_IT(status); 512 | 513 | status = PolySymmetryNode::setValue(fnNode, NUMBER_OF_EDGES, this->meshData.numberOfEdges); 514 | CHECK_MSTATUS_AND_RETURN_IT(status); 515 | 516 | status = PolySymmetryNode::setValue(fnNode, NUMBER_OF_FACES, this->meshData.numberOfFaces); 517 | CHECK_MSTATUS_AND_RETURN_IT(status); 518 | 519 | status = PolySymmetryNode::setValue(fnNode, NUMBER_OF_VERTICES, this->meshData.numberOfVertices); 520 | CHECK_MSTATUS_AND_RETURN_IT(status); 521 | 522 | int vertexChecksum = (int) MeshData::getVertexChecksum(this->selectedMesh); 523 | status = PolySymmetryNode::setValue(fnNode, VERTEX_CHECKSUM, vertexChecksum); 524 | CHECK_MSTATUS_AND_RETURN_IT(status); 525 | 526 | this->setResult(fnNode.name()); 527 | 528 | PolySymmetryCache::addNodeToCache(meshSymmetryNode); 529 | 530 | return MStatus::kSuccess; 531 | } 532 | 533 | 534 | MStatus PolySymmetryCommand::createResultString() 535 | { 536 | stringstream output; 537 | output << "{"; 538 | 539 | output << "\"e\": {"; 540 | setJSONData("symmetry", output, this->meshSymmetryData.edgeSymmetryIndices); 541 | setJSONData("whichSide", output, this->meshSymmetryData.edgeSides, true); 542 | output << "}, "; 543 | 544 | output << "\"f\": {"; 545 | setJSONData("symmetry", output, this->meshSymmetryData.faceSymmetryIndices); 546 | setJSONData("whichSide", output, this->meshSymmetryData.faceSides, true); 547 | output << "}, "; 548 | 549 | output << "\"vtx\": {"; 550 | setJSONData("symmetry", output, this->meshSymmetryData.vertexSymmetryIndices); 551 | setJSONData("whichSide", output, this->meshSymmetryData.vertexSides, true); 552 | output << "}"; 553 | 554 | output << "}"; 555 | 556 | this->setResult(output.str().c_str()); 557 | 558 | return MStatus::kSuccess; 559 | } 560 | 561 | 562 | void PolySymmetryCommand::setJSONData(const char* key, stringstream &output, vector &data, bool isLast) 563 | { 564 | int numberOfItems = (int) data.size(); 565 | 566 | output << " \"" << key << "\"" << ": "; 567 | 568 | output << '['; 569 | 570 | if (numberOfItems > 0) 571 | { 572 | for (int i = 0; i < numberOfItems - 1; i++) 573 | { 574 | output << data[i] << ", "; 575 | } 576 | 577 | output << data[numberOfItems - 1]; 578 | } 579 | 580 | output << ']'; 581 | 582 | if (!isLast) 583 | { 584 | output << ", "; 585 | } 586 | } 587 | 588 | 589 | MStatus PolySymmetryCommand::finalize() 590 | { 591 | MArgList command; 592 | 593 | command.addArg(commandString()); 594 | 595 | for (ComponentSelection &cs : symmetryComponents) 596 | { 597 | command.addArg(SYMMETRY_COMPONENTS_FLAG); 598 | 599 | command.addArg(MString("e[^1s]").format(MString() + cs.edgeIndices.first)); 600 | command.addArg(MString("e[^1s]").format(MString() + cs.edgeIndices.second)); 601 | 602 | command.addArg(MString("f[^1s]").format(MString() + cs.faceIndices.first)); 603 | command.addArg(MString("f[^1s]").format(MString() + cs.faceIndices.second)); 604 | 605 | command.addArg(MString("vtx[^1s]").format(MString() + cs.vertexIndices.first)); 606 | command.addArg(MString("vtx[^1s]").format(MString() + cs.vertexIndices.second)); 607 | } 608 | 609 | for (int &i : leftSideVertexIndices) 610 | { 611 | command.addArg(LEFT_SIDE_VERTEX_FLAG); 612 | command.addArg(MString("vtx[^1s]").format(MString() + i)); 613 | } 614 | 615 | command.addArg(this->selectedMesh.partialPathName()); 616 | 617 | return MPxToolCommand::doFinalize( command ); 618 | } -------------------------------------------------------------------------------- /polySymmetry/scripts/polySymmetry/options.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import json 3 | import webbrowser 4 | 5 | 6 | from maya import OpenMaya 7 | from maya import cmds 8 | from maya import mel 9 | 10 | import polySymmetry.commands as _cmds 11 | import polySymmetry.utils 12 | 13 | _INFO = OpenMaya.MGlobal.displayInfo 14 | _ERR = OpenMaya.MGlobal.displayError 15 | _WARN = OpenMaya.MGlobal.displayWarning 16 | 17 | _HELP_URL = r"https://github.com/yantor3d/{}/wiki" 18 | 19 | 20 | class ButtonCommand(object): 21 | """Options box base class for polySymmetry plugin command.""" 22 | 23 | def __init__(self, name, callback): 24 | self.name = name 25 | self.callback = callback 26 | 27 | def __str__(self): 28 | return self.name 29 | 30 | def __call__(self, *args, **kwargs): 31 | try: 32 | self.callback() 33 | except Exception as e: 34 | OpenMaya.MGlobal.displayError(str(e).strip()) 35 | 36 | 37 | class _OptionBox(object): 38 | """Options box base class for polySymmetry plugin command.""" 39 | 40 | NICE_NAME = "Poly Symmetry" 41 | COMMAND_NAME = "polySymmetry" 42 | 43 | def __init__(self, *args, **kwargs): 44 | windowName = "{}OptionBox".format(self.COMMAND_NAME) 45 | 46 | if cmds.window(windowName, exists=True): 47 | cmds.deleteUI(windowName) 48 | 49 | self.win = cmds.window( 50 | windowName, 51 | title='{} Options'.format(self.NICE_NAME), 52 | menuBar=True, 53 | widthHeight=(546, 350) 54 | ) 55 | 56 | self.editMenu = cmds.menu(label='Edit', parent=self.win) 57 | 58 | cmds.menuItem(label='Save Settings', command=self._handleSaveSettingsClick) 59 | cmds.menuItem(label='Reset Settings', command=self._handleResetSettingsClick) 60 | 61 | self.helpMenu = cmds.menu(label='Help', parent=self.win) 62 | 63 | cmds.menuItem(label='Help on {}'.format(self.NICE_NAME), command=self._handleHelpClick) 64 | 65 | formLayout = cmds.formLayout() 66 | 67 | self.mainLayout = cmds.formLayout(parent=formLayout) 68 | 69 | buttonLayout = cmds.formLayout(parent=formLayout, numberOfDivisions=100) 70 | 71 | self.applyAndCloseBtn = cmds.button( 72 | recomputeSize=0, 73 | height=26, 74 | label='Apply And Close', 75 | command=ButtonCommand(self.COMMAND_NAME, self._handleApplyAndCloseClick) 76 | ) 77 | 78 | self.applyBtn = cmds.button( 79 | recomputeSize=0, 80 | height=26, 81 | label='Apply', 82 | command=ButtonCommand(self.COMMAND_NAME, self._handleApplyClick) 83 | ) 84 | 85 | self.closeBtn = cmds.button( 86 | recomputeSize=0, 87 | height=26, 88 | label='Close', 89 | command=self._handleCloseClick 90 | ) 91 | 92 | 93 | cmds.formLayout( 94 | buttonLayout, 95 | edit=True, 96 | attachForm=( 97 | (self.applyAndCloseBtn, "top", 0), 98 | (self.applyAndCloseBtn, "left", 0), 99 | (self.applyAndCloseBtn, "bottom", 0), 100 | 101 | (self.applyBtn, "top", 0), 102 | (self.applyBtn, "bottom", 0), 103 | 104 | (self.closeBtn, "top", 0), 105 | (self.closeBtn, "right", 0), 106 | (self.closeBtn, "bottom", 0), 107 | ), 108 | attachPosition=( 109 | (self.applyAndCloseBtn, "right", 2, 33), 110 | (self.applyBtn, "left", 2, 33), 111 | (self.applyBtn, "right", 2, 67), 112 | (self.closeBtn, "left", 2, 67), 113 | ) 114 | ) 115 | 116 | cmds.formLayout( 117 | formLayout, 118 | edit=True, 119 | attachForm=( 120 | (self.mainLayout, "top", 0), 121 | (self.mainLayout, "left", 0), 122 | (self.mainLayout, "right", 0), 123 | 124 | (buttonLayout, "left", 4), 125 | (buttonLayout, "bottom", 4), 126 | (buttonLayout, "right", 4), 127 | ), 128 | attachNone=( 129 | (buttonLayout, "top") 130 | ), 131 | attachControl=( 132 | (self.mainLayout, "bottom", 4, buttonLayout) 133 | ) 134 | ) 135 | 136 | self.initUI() 137 | self.show() 138 | 139 | def initUI(self): 140 | mainLayout= cmds.tabLayout(tabsVisible=0, parent=self.mainLayout) 141 | cmds.tabLayout(tabsVisible=0) 142 | 143 | cmds.formLayout( 144 | self.mainLayout, 145 | edit=True, 146 | attachForm=( 147 | (mainLayout, 'top', 0), 148 | (mainLayout, 'left', 0), 149 | (mainLayout, 'bottom', 0), 150 | (mainLayout, 'right', 0) 151 | ) 152 | ) 153 | 154 | def show(self): 155 | if cmds.window(self.win, exists=True): 156 | cmds.showWindow(self.win) 157 | self.loadOptions() 158 | 159 | def close(self, *args): 160 | if cmds.window(self.win, exists=True): 161 | cmds.evalDeferred('cmds.deleteUI("{}")'.format(self.win)) 162 | 163 | def _handleApplyClick(self, *args): 164 | try: 165 | self.doIt() 166 | except polySymmetry.utils.InvalidSelection as e: 167 | _ERR(e) 168 | else: 169 | self.saveOptions() 170 | 171 | def _handleApplyAndCloseClick(self, *args): 172 | try: 173 | self.doIt() 174 | except polySymmetry.utils.InvalidSelection as e: 175 | _ERR(e) 176 | else: 177 | self.saveOptions() 178 | self.close() 179 | 180 | def _handleCloseClick(self, *args): 181 | self.close() 182 | 183 | def _handleSaveSettingsClick(self, *args): 184 | self.saveOptions() 185 | 186 | def _handleResetSettingsClick(self, *args): 187 | self.resetOptions() 188 | 189 | def _handleHelpClick(self, *args): 190 | self.help() 191 | 192 | def help(self): 193 | helpUrl = _HELP_URL.format(self.COMMAND_NAME) 194 | webbrowser.open(helpUrl) 195 | 196 | def doIt(self): 197 | raise NotImplementedError() 198 | 199 | def saveOptions(self): 200 | options = self.getOptions() 201 | polySymmetry.utils.saveOptions(self.COMMAND_NAME, options) 202 | 203 | def loadOptions(self): 204 | options = polySymmetry.utils.loadOptions(self.COMMAND_NAME) 205 | self.setOptions(options) 206 | 207 | def resetOptions(self): 208 | polySymmetry.utils.deleteOptions(self.COMMAND_NAME) 209 | self.setOptions({}) 210 | 211 | def setOptions(self, options): 212 | pass 213 | 214 | def getOptions(self): 215 | return {} 216 | 217 | 218 | class PolyDeformerWeightsOptionBox(_OptionBox): 219 | """Options box for polyDeformerWeights command.""" 220 | 221 | NICE_NAME = "Poly Deformer Weights" 222 | COMMAND_NAME = "polyDeformerWeights" 223 | 224 | def __init__(self, *args, **kwargs): 225 | super(self.__class__, self).__init__(*args, **kwargs) 226 | 227 | if 'default_action' in kwargs: 228 | _setChoice(self.actionWidget, kwargs['default_action']) 229 | self._refreshUI() 230 | 231 | def initUI(self): 232 | super(self.__class__, self).initUI() 233 | 234 | cmds.columnLayout(adjustableColumn=True, rowSpacing=3) 235 | 236 | self.actionWidget = cmds.radioButtonGrp( 237 | label='Action', 238 | numberOfRadioButtons=3, 239 | label1='Copy', 240 | label2='Flip', 241 | label3='Mirror', 242 | select=3, 243 | changeCommand=self._refreshUI 244 | ) 245 | 246 | self.directionWidget = cmds.radioButtonGrp( 247 | label="Direction", 248 | numberOfRadioButtons=2, 249 | label1="Left to Right", 250 | label2="Right To Left", 251 | select=1 252 | ) 253 | 254 | cmds.separator() 255 | 256 | self.sourceMeshWidget = cmds.textFieldButtonGrp( 257 | label='Source Mesh', 258 | buttonLabel='<<<', 259 | text="", 260 | editable=False 261 | ) 262 | 263 | self.sourceDeformerWidget = cmds.textFieldGrp( 264 | label='Source Deformer', 265 | text='', 266 | editable=False 267 | ) 268 | 269 | cmds.textFieldButtonGrp( 270 | self.sourceMeshWidget, 271 | edit=True, 272 | buttonCommand=functools.partial( 273 | self._handleLoadSelectedMeshClick, 274 | meshWidget=self.sourceMeshWidget, 275 | deformerWidget=self.sourceDeformerWidget 276 | ) 277 | ) 278 | 279 | cmds.popupMenu( 280 | postMenuCommand=functools.partial( 281 | self._handleDeformerMenuOpen, 282 | meshWidget=self.sourceMeshWidget, 283 | deformerWidget=self.sourceDeformerWidget 284 | ) 285 | ) 286 | 287 | cmds.separator() 288 | 289 | self.destinationMeshWidget = cmds.textFieldButtonGrp( 290 | label='Destination Mesh', 291 | buttonLabel='<<<', 292 | text="", 293 | editable=False 294 | ) 295 | 296 | self.destinationDeformerWidget = cmds.textFieldGrp( 297 | label='Destination Deformer', 298 | text='', 299 | editable=False 300 | ) 301 | 302 | cmds.textFieldButtonGrp( 303 | self.destinationMeshWidget, 304 | edit=True, 305 | buttonCommand=functools.partial( 306 | self._handleLoadSelectedMeshClick, 307 | meshWidget=self.destinationMeshWidget, 308 | deformerWidget=self.destinationDeformerWidget 309 | ) 310 | ) 311 | 312 | cmds.popupMenu( 313 | postMenuCommand=functools.partial( 314 | self._handleDeformerMenuOpen, 315 | meshWidget=self.destinationMeshWidget, 316 | deformerWidget=self.destinationDeformerWidget 317 | ) 318 | ) 319 | 320 | def _handleLoadSelectedMeshClick(self, *args, **kwargs): 321 | meshWidget = kwargs['meshWidget'] 322 | deformerWidget = kwargs['deformerWidget'] 323 | 324 | selectedMesh = _getText(meshWidget) 325 | meshes = _cmds._getSelectedMeshes() 326 | 327 | if meshes: 328 | if meshes[0] != selectedMesh: 329 | _setText(meshWidget, meshes[0]) 330 | _setText(deformerWidget, "") 331 | else: 332 | cmds.warning("Must select a mesh") 333 | 334 | def _handleDeformerMenuOpen(self, *args, **kwargs): 335 | menu = args[0] 336 | 337 | meshWidget = kwargs['meshWidget'] 338 | deformerWidget = kwargs['deformerWidget'] 339 | 340 | selectedMesh = _getText(meshWidget) 341 | 342 | menuItems = cmds.popupMenu(menu, query=True, itemArray=True) or [] 343 | 344 | for item in menuItems: 345 | cmds.deleteUI(item) 346 | 347 | if selectedMesh: 348 | deformers = cmds.ls( 349 | cmds.listHistory(selectedMesh), 350 | type="weightGeometryFilter", 351 | ) 352 | 353 | if deformers: 354 | for d in deformers: 355 | cmds.menuItem( 356 | d, 357 | parent=menu, 358 | command=functools.partial( 359 | self._handleDeformerMenuItemClick, 360 | textFieldGrp=deformerWidget, 361 | text=d 362 | ) 363 | ) 364 | else: 365 | cmds.menuItem("Specified mesh is not deformed.") 366 | else: 367 | cmds.menuItem("No mesh specified.") 368 | 369 | def _handleDeformerMenuItemClick(self, *args, **kwargs): 370 | _setText(kwargs['textFieldGrp'], kwargs['text']) 371 | 372 | def _refreshUI(self, *args): 373 | action = _getChoice(self.actionWidget) 374 | 375 | cmds.radioButtonGrp( 376 | self.directionWidget, 377 | edit=True, 378 | enable=action == 3 379 | ) 380 | 381 | cmds.textFieldButtonGrp( 382 | self.destinationMeshWidget, 383 | edit=True, 384 | visible=action == 1 385 | ) 386 | 387 | cmds.textFieldGrp( 388 | self.destinationDeformerWidget, 389 | edit=True, 390 | visible=action == 1 391 | ) 392 | 393 | def doIt(self): 394 | options = self.getOptions() 395 | 396 | kwargs = {} 397 | 398 | if options['action'] == 2: 399 | kwargs['flip'] = True 400 | elif options['action'] == 3: 401 | kwargs['mirror'] = True 402 | 403 | kwargs['direction'] = 1 if options['direction'] == 1 else -1 404 | 405 | args = [] 406 | 407 | sourceMesh = _getText(self.sourceMeshWidget) 408 | sourceDeformer = _getText(self.sourceDeformerWidget) 409 | 410 | destinationMesh = _getText(self.destinationMeshWidget) 411 | destinationDeformer = _getText(self.destinationDeformerWidget) 412 | 413 | if sourceMesh: 414 | if sourceDeformer: 415 | args += [sourceMesh, sourceDeformer] 416 | else: 417 | OpenMaya.MGlobal.displayError("Must specify a deformer") 418 | return 419 | 420 | if options['action'] == 1: 421 | if destinationMesh: 422 | if destinationDeformer: 423 | args += [destinationMesh, destinationDeformer] 424 | else: 425 | OpenMaya.MGlobal.displayError("Must specify a deformer") 426 | return 427 | 428 | _cmds.copyPolyDeformerWeights(*args) 429 | else: 430 | _cmds.polyDeformerWeights(*args, **kwargs) 431 | 432 | def getOptions(self): 433 | return { 434 | "direction": _getChoice(self.directionWidget), 435 | "action": _getChoice(self.actionWidget) 436 | } 437 | 438 | def setOptions(self, options): 439 | _setChoice(self.directionWidget, options.get('direction', 1)) 440 | _setChoice(self.actionWidget, options.get('action', 3)) 441 | 442 | self._refreshUI() 443 | 444 | 445 | class InfluenceSymmetryOptionsBox(_OptionBox): 446 | """Options box for polySkinWeights -influenceSymmetry command.""" 447 | 448 | NICE_NAME = "Influence Symmetry" 449 | COMMAND_NAME = "influenceSymmetry" 450 | 451 | def initUI(self): 452 | super(self.__class__, self).initUI() 453 | 454 | cmds.columnLayout(adjustableColumn=True, rowSpacing=3) 455 | 456 | self.leftPatternWidget = cmds.textFieldGrp( 457 | label='Left Pattern', 458 | text='L_*' 459 | ) 460 | 461 | self.rightPatternWidget = cmds.textFieldGrp( 462 | label='Right Pattern', 463 | text='R_*' 464 | ) 465 | 466 | def doIt(self): 467 | options = self.getOptions() 468 | 469 | _cmds.setInfluenceSymmetry(**options) 470 | 471 | def getOptions(self): 472 | return { 473 | "leftPattern": _getText(self.leftPatternWidget), 474 | "rightPattern": _getText(self.rightPatternWidget) 475 | } 476 | 477 | def setOptions(self, options): 478 | _setText(self.leftPatternWidget, options.get('leftPattern', 'L_*')) 479 | _setText(self.rightPatternWidget, options.get('rightPattern', 'R_*')) 480 | 481 | 482 | class PolySkinWeightsOptionBox(_OptionBox): 483 | """Options box for polySkinWeights command.""" 484 | 485 | NICE_NAME = "Poly Skin Weights" 486 | COMMAND_NAME = "polySkinWeights" 487 | 488 | def __init__(self, *args, **kwargs): 489 | super(self.__class__, self).__init__(*args, **kwargs) 490 | 491 | if 'default_action' in kwargs: 492 | _setChoice(self.actionWidget, kwargs['default_action']) 493 | self._refreshUI() 494 | 495 | def initUI(self): 496 | super(self.__class__, self).initUI() 497 | 498 | cmds.columnLayout(adjustableColumn=True, rowSpacing=3) 499 | 500 | self.actionWidget = cmds.radioButtonGrp( 501 | label='Action', 502 | numberOfRadioButtons=3, 503 | label1='Copy', 504 | label2='Flip', 505 | label3='Mirror', 506 | select=3, 507 | changeCommand=self._refreshUI 508 | ) 509 | 510 | self.directionWidget = cmds.radioButtonGrp( 511 | label="Direction", 512 | numberOfRadioButtons=2, 513 | label1="Left to Right", 514 | label2="Right To Left", 515 | select=1 516 | ) 517 | 518 | cmds.separator() 519 | 520 | self.useInfluencePatternWidget = cmds.checkBoxGrp( 521 | label='Use Influence Pattern', 522 | changeCommand=self._refreshUI, 523 | numberOfCheckBoxes=1, 524 | value1=1 525 | ) 526 | 527 | self.leftPatternWidget = cmds.textFieldGrp( 528 | label='Left Pattern', 529 | text='L_*' 530 | ) 531 | 532 | self.rightPatternWidget = cmds.textFieldGrp( 533 | label='Right Pattern', 534 | text='R_*' 535 | ) 536 | 537 | cmds.separator() 538 | 539 | self.normalizeWidget = cmds.checkBoxGrp( 540 | label='Normalize', 541 | numberOfCheckBoxes=1 542 | ) 543 | 544 | def _refreshUI(self, *args): 545 | action = _getChoice(self.actionWidget) 546 | 547 | cmds.radioButtonGrp( 548 | self.directionWidget, 549 | edit=True, 550 | enable=action == 3 551 | ) 552 | 553 | useInfluencePattern = _getChecked(self.useInfluencePatternWidget) 554 | 555 | cmds.textFieldGrp( 556 | self.leftPatternWidget, 557 | edit=True, 558 | enable=useInfluencePattern 559 | ) 560 | 561 | cmds.textFieldGrp( 562 | self.rightPatternWidget, 563 | edit=True, 564 | enable=useInfluencePattern 565 | ) 566 | 567 | def doIt(self): 568 | options = self.getOptions() 569 | 570 | kwargs = {} 571 | 572 | if options['normalize']: 573 | kwargs['normalize'] = True 574 | 575 | if options['action'] == 2: 576 | kwargs['flip'] = True 577 | elif options['action'] == 3: 578 | kwargs['mirror'] = True 579 | 580 | if options['useInfluencePattern']: 581 | kwargs['influenceSymmetry'] = ( 582 | options['leftPattern'], 583 | options['rightPattern'] 584 | ) 585 | 586 | kwargs['direction'] = 1 if options['direction'] == 1 else -1 587 | 588 | _cmds.mirrorPolySkinWeights(**kwargs) 589 | 590 | def getOptions(self): 591 | return { 592 | "normalize": _getChecked(self.normalizeWidget), 593 | "useInfluencePattern": _getChecked(self.useInfluencePatternWidget), 594 | "leftPattern": _getText(self.leftPatternWidget), 595 | "rightPattern": _getText(self.rightPatternWidget), 596 | "direction": _getChoice(self.directionWidget), 597 | "action": _getChoice(self.actionWidget) 598 | } 599 | 600 | def setOptions(self, options): 601 | _setText(self.leftPatternWidget, options.get('leftPattern', 'L_*')) 602 | _setText(self.rightPatternWidget, options.get('rightPattern', 'R_*')) 603 | 604 | _setChecked( 605 | self.useInfluencePatternWidget, 606 | options.get('useInfluencePattern', True) 607 | ) 608 | 609 | _setChecked(self.normalizeWidget, options.get('normalize', True)) 610 | 611 | _setChoice(self.directionWidget, options.get('direction', 1)) 612 | _setChoice(self.actionWidget, options.get('action', 3)) 613 | 614 | self._refreshUI() 615 | 616 | 617 | def copyPolyDeformerWeightsOptions(): 618 | PolyDeformerWeightsOptionBox(default_action=1) 619 | 620 | 621 | def flipPolyDeformerWeightsOptions(): 622 | PolyDeformerWeightsOptionBox(default_action=2) 623 | 624 | 625 | def mirrorPolyDeformerWeightsOptions(): 626 | PolyDeformerWeightsOptionBox(default_action=3) 627 | 628 | 629 | def copyPolySkinWeightsOptions(): 630 | PolySkinWeightsOptionBox(default_action=1) 631 | 632 | 633 | def mirrorPolySkinWeightsOptions(): 634 | PolySkinWeightsOptionBox(default_action=3) 635 | 636 | 637 | def _getText(widget): 638 | return cmds.textFieldGrp(widget, query=True, text=True) 639 | 640 | 641 | def _setText(widget, text): 642 | cmds.textFieldGrp(widget, edit=True, text=text) 643 | 644 | 645 | def _getChecked(widget): 646 | return cmds.checkBoxGrp(widget, query=True, value1=True) 647 | 648 | 649 | def _setChecked(widget, state): 650 | cmds.checkBoxGrp(widget, edit=True, value1=state) 651 | 652 | 653 | def _getChoice(widget): 654 | return cmds.radioButtonGrp(widget, query=True, select=True) 655 | 656 | 657 | def _setChoice(widget, choice): 658 | cmds.radioButtonGrp(widget, edit=True, select=int(choice)) -------------------------------------------------------------------------------- /src/polySkinWeights.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (c) 2017 Ryan Porter 3 | You may use, distribute, or modify this code under the terms of the MIT license. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "parseArgs.h" 16 | #include "polySkinWeights.h" 17 | #include "polySymmetryNode.h" 18 | #include "sceneCache.h" 19 | #include "selection.h" 20 | 21 | #include "../pystring/pystring.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | using namespace std; 50 | 51 | // Indicates the source deformed mesh. 52 | #define SOURCE_MESH_FLAG "-sm" 53 | #define SOURCE_MESH_LONG_FLAG "-sourceMesh" 54 | 55 | // Indicates the source deformer. 56 | #define SOURCE_SKIN_FLAG "-ss" 57 | #define SOURCE_SKIN_LONG_FLAG "-sourceSkin" 58 | 59 | // Indicates the destination deformed shape. 60 | #define DESTINATION_MESH_FLAG "-dm" 61 | #define DESTINATION_MESH_LONG_FLAG "-destinationMesh" 62 | 63 | // Indicates the destination deformer. 64 | #define DESTINATION_SKIN_FLAG "-ds" 65 | #define DESTINATION_SKIN_LONG_FLAG "-destinationSkin" 66 | 67 | // Indicates the direction of the mirror or flip action - 1 (left to right) or -1 (right to left) 68 | #define DIRECTION_FLAG "-d" 69 | #define DIRECTION_LONG_FLAG "-direction" 70 | 71 | // Indicates that the influences on the specified skinCluster should be tagged left/right/center 72 | #define INFLUENCE_SYMMETRY_FLAG "-inf" 73 | #define INFLUENCE_SYMMETRY_LONG_FLAG "-influenceSymmetry" 74 | 75 | // Indicates that the deformer weights should be mirrored. 76 | #define MIRROR_FLAG "-m" 77 | #define MIRROR_LONG_FLAG "-mirror" 78 | 79 | // Indicates that the skin weights should be normalized. 80 | #define NORMALIZE_FLAG "-nr" 81 | #define NORMALIZE_LONG_FLAG "-normalize" 82 | 83 | // Indicates that the deformer weights should be flipped. 84 | #define FLIP_FLAG "-f" 85 | #define FLIP_LONG_FLAG "-flip" 86 | 87 | #define CENTER_SIDE 0 88 | #define LEFT_SIDE 1 89 | #define RIGHT_SIDE 2 90 | #define NONE_SIDE 3 91 | #define OTHER_TYPE 18 92 | 93 | #define RETURN_IF_ERROR(s) if (!s) { return s; } 94 | 95 | PolySkinWeightsCommand::PolySkinWeightsCommand() {} 96 | PolySkinWeightsCommand::~PolySkinWeightsCommand() 97 | { 98 | influenceSymmetry.clear(); 99 | oldWeights.clear(); 100 | newWeights.clear(); 101 | 102 | oldWeightValues.setLength(0); 103 | } 104 | 105 | void* PolySkinWeightsCommand::creator() 106 | { 107 | return new PolySkinWeightsCommand(); 108 | } 109 | 110 | 111 | MSyntax PolySkinWeightsCommand::getSyntax() 112 | { 113 | MSyntax syntax; 114 | 115 | syntax.setObjectType(MSyntax::kSelectionList, 0, 1); 116 | syntax.useSelectionAsDefault(true); 117 | 118 | syntax.addFlag(SOURCE_MESH_FLAG, SOURCE_MESH_LONG_FLAG, MSyntax::kString); 119 | syntax.addFlag(SOURCE_SKIN_FLAG, SOURCE_SKIN_LONG_FLAG, MSyntax::kString); 120 | 121 | syntax.addFlag(DESTINATION_MESH_FLAG, DESTINATION_MESH_LONG_FLAG, MSyntax::kString); 122 | syntax.addFlag(DESTINATION_SKIN_FLAG, DESTINATION_SKIN_LONG_FLAG, MSyntax::kString); 123 | 124 | syntax.addFlag(INFLUENCE_SYMMETRY_FLAG, INFLUENCE_SYMMETRY_LONG_FLAG, MSyntax::kString, MSyntax::kString); 125 | syntax.makeFlagQueryWithFullArgs(INFLUENCE_SYMMETRY_FLAG, true); 126 | 127 | syntax.addFlag(DIRECTION_FLAG, DIRECTION_LONG_FLAG, MSyntax::kLong); 128 | syntax.addFlag(FLIP_FLAG, FLIP_LONG_FLAG); 129 | syntax.addFlag(MIRROR_FLAG, MIRROR_LONG_FLAG); 130 | syntax.addFlag(NORMALIZE_FLAG, NORMALIZE_LONG_FLAG); 131 | 132 | syntax.enableQuery(true); 133 | syntax.enableEdit(true); 134 | 135 | return syntax; 136 | } 137 | 138 | 139 | /* Unpack the command arguments */ 140 | MStatus PolySkinWeightsCommand::parseArguments(MArgDatabase &argsData) 141 | { 142 | MStatus status; 143 | 144 | status = parseArgs::getNodeArgument(argsData, SOURCE_SKIN_FLAG, this->sourceSkin, true); 145 | CHECK_MSTATUS_AND_RETURN_IT(status); 146 | 147 | status = parseArgs::getNodeArgument(argsData, DESTINATION_SKIN_FLAG, this->destinationSkin, false); 148 | CHECK_MSTATUS_AND_RETURN_IT(status); 149 | 150 | status = parseArgs::getDagPathArgument(argsData, SOURCE_MESH_FLAG, this->sourceMesh, true); 151 | CHECK_MSTATUS_AND_RETURN_IT(status); 152 | 153 | status = parseArgs::getDagPathArgument(argsData, DESTINATION_MESH_FLAG, this->destinationMesh, false); 154 | CHECK_MSTATUS_AND_RETURN_IT(status); 155 | 156 | if (argsData.isFlagSet(INFLUENCE_SYMMETRY_FLAG)) 157 | { 158 | this->parseInfluenceSymmetryArgument(argsData); 159 | } 160 | 161 | this->direction = argsData.isFlagSet(DIRECTION_FLAG) ? argsData.flagArgumentInt(DIRECTION_FLAG, 0, &status) : 1; 162 | 163 | this->mirrorWeights = argsData.isFlagSet(MIRROR_FLAG); 164 | this->flipWeights = argsData.isFlagSet(FLIP_FLAG); 165 | this->normalizeWeights = argsData.isFlagSet(NORMALIZE_FLAG); 166 | 167 | return MStatus::kSuccess; 168 | } 169 | 170 | 171 | /* Unpack the edit arguments */ 172 | MStatus PolySkinWeightsCommand::parseEditArguments(MArgDatabase &argsData) 173 | { 174 | MStatus status; 175 | 176 | status = this->parseQueryArguments(argsData); 177 | RETURN_IF_ERROR(status); 178 | 179 | if (!argsData.isFlagSet(INFLUENCE_SYMMETRY_FLAG)) 180 | { 181 | MString errorMsg("^1s/^2s flag is required in edit mode."); 182 | errorMsg.format(errorMsg, MString(INFLUENCE_SYMMETRY_LONG_FLAG), MString(INFLUENCE_SYMMETRY_FLAG)); 183 | 184 | MGlobal::displayError(errorMsg); 185 | return MStatus::kFailure; 186 | } 187 | 188 | this->parseInfluenceSymmetryArgument(argsData); 189 | 190 | return MStatus::kSuccess; 191 | } 192 | 193 | 194 | /* Unpack the query arguments */ 195 | MStatus PolySkinWeightsCommand::parseQueryArguments(MArgDatabase &argsData) 196 | { 197 | MStatus status; 198 | 199 | MSelectionList selection; 200 | argsData.getObjects(selection); 201 | 202 | MDagPath dagPath; 203 | MObject object; 204 | 205 | status = selection.getDagPath(0, dagPath); 206 | 207 | if (!argsData.isFlagSet(INFLUENCE_SYMMETRY_FLAG)) 208 | { 209 | MString errorMsg("The ^1s/^2s flag is required in query mode."); 210 | errorMsg.format( 211 | errorMsg, 212 | MString(INFLUENCE_SYMMETRY_LONG_FLAG), 213 | MString(INFLUENCE_SYMMETRY_FLAG) 214 | ); 215 | 216 | MGlobal::displayError(errorMsg); 217 | return MStatus::kFailure; 218 | } 219 | 220 | this->parseInfluenceSymmetryArgument(argsData); 221 | 222 | if (status && parseArgs::isNodeType(dagPath, MFn::kMesh)) 223 | { 224 | if (!dagPath.node().hasFn(MFn::kMesh)) 225 | { 226 | dagPath.extendToShapeDirectlyBelow(0); 227 | } 228 | 229 | object = dagPath.node(); 230 | 231 | MItDependencyGraph iterGraph( 232 | object, 233 | MFn::kSkinClusterFilter, 234 | MItDependencyGraph::kUpstream, 235 | MItDependencyGraph::kDepthFirst, 236 | MItDependencyGraph::kNodeLevel, 237 | &status 238 | ); 239 | 240 | CHECK_MSTATUS_AND_RETURN_IT(status); 241 | 242 | while (!iterGraph.isDone()) 243 | { 244 | MObject obj = iterGraph.currentItem(); 245 | 246 | if (obj.hasFn(MFn::kSkinClusterFilter)) 247 | { 248 | this->sourceSkin = obj; 249 | break; 250 | } 251 | iterGraph.next(); 252 | } 253 | } else { 254 | status = selection.getDependNode(0, object); 255 | 256 | if (status && parseArgs::isNodeType(object, MFn::kSkinClusterFilter)) 257 | { 258 | this->sourceSkin = object; 259 | } 260 | } 261 | 262 | return MStatus::kSuccess; 263 | } 264 | 265 | MStatus PolySkinWeightsCommand::parseInfluenceSymmetryArgument(MArgDatabase &argsData) 266 | { 267 | MStatus status; 268 | 269 | if (argsData.isFlagSet(INFLUENCE_SYMMETRY_FLAG)) 270 | { 271 | MString leftInfluencePatternArg; 272 | MString rightInfluencePatternArg; 273 | 274 | MStatus s0 = argsData.getFlagArgument(INFLUENCE_SYMMETRY_FLAG, 0, leftInfluencePatternArg); 275 | MStatus s1 = argsData.getFlagArgument(INFLUENCE_SYMMETRY_FLAG, 1, rightInfluencePatternArg); 276 | 277 | if (s0 && s1) 278 | { 279 | leftInfluencePattern = string(leftInfluencePatternArg.asChar()); 280 | rightInfluencePattern = string(rightInfluencePatternArg.asChar()); 281 | 282 | isInfluenceSymmetryFlagSet = true; 283 | } else { 284 | isQueryInfluenceSymmetry = this->isQuery; 285 | } 286 | } 287 | 288 | return MStatus::kSuccess; 289 | } 290 | 291 | 292 | /* Validate the command arguments */ 293 | MStatus PolySkinWeightsCommand::validateArguments() 294 | { 295 | MStatus status; 296 | 297 | if (this->isInfluenceSymmetryFlagSet) 298 | { 299 | status = this->validateInfluenceSymmetryArgument(); 300 | RETURN_IF_ERROR(status); 301 | } 302 | 303 | // Direction msut be left to right (1) or right to left (-1) 304 | if (this->direction != 1 && this->direction != -1) 305 | { 306 | MString errorMsg("^1s/^2s flag should be 1 (left to right) or -1 (right to left)"); 307 | errorMsg.format(errorMsg, MString(DIRECTION_LONG_FLAG), MString(DIRECTION_FLAG)); 308 | 309 | MGlobal::displayError(errorMsg); 310 | return MStatus::kFailure; 311 | } 312 | 313 | // Source mesh must be a mesh 314 | if (!parseArgs::isNodeType(this->sourceMesh, MFn::kMesh)) 315 | { 316 | MString errorMsg("A mesh node should be specified with the ^1s/^2s flag."); 317 | errorMsg.format(errorMsg, MString(SOURCE_MESH_LONG_FLAG), MString(SOURCE_MESH_FLAG)); 318 | 319 | MGlobal::displayError(errorMsg); 320 | return MStatus::kFailure; 321 | } 322 | 323 | // Destination mesh must be a mesh, and be point compatible with the source mesh. 324 | // If no destination mesh is specified, use the source mesh instead. 325 | if (destinationMesh.node().isNull()) 326 | { 327 | destinationMesh.set(sourceMesh); 328 | } else if (!destinationMesh.hasFn(MFn::kMesh)) { 329 | MString errorMsg("A mesh node should be specified with the ^1s/^2s flag."); 330 | errorMsg.format(errorMsg, MString(DESTINATION_MESH_LONG_FLAG), MString(DESTINATION_MESH_FLAG)); 331 | 332 | MGlobal::displayError(errorMsg); 333 | return MStatus::kFailure; 334 | } else { 335 | MFnMesh fnSourceMesh(this->sourceMesh); 336 | MFnMesh fnDestinationMesh(this->destinationMesh); 337 | 338 | if (fnSourceMesh.numVertices() != fnDestinationMesh.numVertices()) 339 | { 340 | MString errorMsg("Source mesh and destination mesh are not point compatible. Cannot continue."); 341 | MGlobal::displayError(errorMsg); 342 | return MStatus::kFailure; 343 | } 344 | } 345 | 346 | // Source/destination mesh must have polySymmetryData cached if the weights are going to be mirrored or flipped. 347 | if (mirrorWeights || flipWeights) 348 | { 349 | bool cacheHit = PolySymmetryCache::getNodeFromCache(this->sourceMesh, this->polySymmetryData); 350 | 351 | if (!cacheHit) 352 | { 353 | MString errorMsg("Mesh specified with the ^1s/^2s flag must have an associated ^3s node."); 354 | errorMsg.format(errorMsg, MString(SOURCE_MESH_LONG_FLAG), MString(SOURCE_MESH_FLAG), PolySymmetryNode::NODE_NAME); 355 | 356 | MGlobal::displayError(errorMsg); 357 | return MStatus::kFailure; 358 | } 359 | } 360 | 361 | this->numberOfVertices = MFnMesh(sourceMesh).numVertices(); 362 | 363 | MSelectionList activeSelection; 364 | MSelectionList vertexSelection; 365 | 366 | MGlobal::getActiveSelectionList(activeSelection); 367 | 368 | if (destinationMesh.node().hasFn(MFn::kMesh)) { destinationMesh.pop(); } 369 | getSelectedComponents(destinationMesh, activeSelection, vertexSelection, MFn::Type::kMeshVertComponent); 370 | 371 | MObject selectedVertices; 372 | 373 | if (!vertexSelection.isEmpty()) 374 | { 375 | vertexSelection.getDagPath(0, destinationMesh, selectedVertices); 376 | } 377 | 378 | MItGeometry itGeo(destinationMesh, selectedVertices); 379 | uint numSelectedVertices = itGeo.exactCount(); 380 | 381 | vector vertexIsSelected(numberOfVertices); 382 | selectedVertexIndices.resize(numberOfVertices, -1); 383 | 384 | int idx = 0; 385 | 386 | while (!itGeo.isDone()) 387 | { 388 | int i = itGeo.index(); 389 | selectedVertexIndices[idx++] = i; 390 | vertexIsSelected[i] = true; 391 | itGeo.next(); 392 | } 393 | 394 | if (!selectedVertices.isNull() && !polySymmetryData.isNull()) 395 | { 396 | vector vertexSymmetry; 397 | 398 | MFnDependencyNode fnNode(this->polySymmetryData); 399 | PolySymmetryNode::getValues(fnNode, VERTEX_SYMMETRY, vertexSymmetry); 400 | 401 | for (uint i = 0; i < numSelectedVertices; i++) 402 | { 403 | int v = selectedVertexIndices[i]; 404 | int sv = vertexSymmetry[v]; 405 | 406 | if (!vertexIsSelected[sv]) 407 | { 408 | selectedVertexIndices[idx++] = sv; 409 | } 410 | } 411 | } 412 | 413 | selectedVertexIndices.resize(idx); 414 | 415 | if (!sourceMesh.node().hasFn(MFn::kMesh)) 416 | { 417 | sourceMesh.extendToShapeDirectlyBelow(0); 418 | } 419 | 420 | if (!destinationMesh.node().hasFn(MFn::kMesh)) 421 | { 422 | destinationMesh.extendToShapeDirectlyBelow(0); 423 | } 424 | 425 | // Source skin must be a skinCluster. 426 | if (!parseArgs::isNodeType(sourceSkin, MFn::kSkinClusterFilter)) 427 | { 428 | MString errorMsg("A skinCluster node should be specified with the ^1s/^2s flag."); 429 | errorMsg.format(errorMsg, MString(SOURCE_SKIN_LONG_FLAG), MString(SOURCE_SKIN_FLAG)); 430 | 431 | MGlobal::displayError(errorMsg); 432 | return MStatus::kFailure; 433 | } 434 | 435 | // Destination skin must be a skinCluster. 436 | // If no destination skin is specified, use the source skin. 437 | if (destinationSkin.isNull()) 438 | { 439 | if (this->sourceMesh == this->destinationMesh) 440 | { 441 | destinationSkin = sourceSkin; 442 | } 443 | } 444 | 445 | if (destinationSkin.isNull() || !destinationSkin.hasFn(MFn::kSkinClusterFilter)) 446 | { 447 | MString errorMsg("A skinCluster node should be specified with the ^1s/^2s flag."); 448 | errorMsg.format(errorMsg, MString(DESTINATION_SKIN_LONG_FLAG), MString(DESTINATION_SKIN_FLAG)); 449 | 450 | MGlobal::displayError(errorMsg); 451 | return MStatus::kFailure; 452 | } 453 | 454 | // Source mesh must be deformed by the source skin cluster. 455 | if (!isDeformedBy(sourceSkin, sourceMesh)) 456 | { 457 | MString errorMsg("^1s is not deformed by ^2s."); 458 | errorMsg.format(errorMsg, sourceMesh.partialPathName(), MFnSkinCluster(sourceSkin).name()); 459 | 460 | MGlobal::displayError(errorMsg); 461 | return MStatus::kFailure; 462 | } 463 | 464 | // Destination mesh must be deformed by the destination skin cluster. 465 | if (!isDeformedBy(destinationSkin, destinationMesh)) 466 | { 467 | MString errorMsg("^1s is not deformed by ^2s."); 468 | errorMsg.format(errorMsg, destinationMesh.partialPathName(), MFnSkinCluster(destinationSkin).name()); 469 | 470 | MGlobal::displayError(errorMsg); 471 | return MStatus::kFailure; 472 | } 473 | 474 | return MStatus::kSuccess; 475 | } 476 | 477 | 478 | /* Validate the query arguments. */ 479 | MStatus PolySkinWeightsCommand::validateEditArguments() 480 | { 481 | MStatus status; 482 | 483 | if (this->sourceSkin.isNull()) 484 | { 485 | MGlobal::displayError("polySkinWeights -edit requires a mesh or skinCluster "); 486 | return MStatus::kFailure; 487 | } 488 | 489 | status = this->validateInfluenceSymmetryArgument(); 490 | RETURN_IF_ERROR(status); 491 | 492 | return MStatus::kSuccess; 493 | } 494 | 495 | 496 | /* Validate the query arguments. */ 497 | MStatus PolySkinWeightsCommand::validateQueryArguments() 498 | { 499 | MStatus status; 500 | 501 | if (this->sourceSkin.isNull()) 502 | { 503 | MGlobal::displayError("polySkinWeights -query requires a mesh or skinCluster "); 504 | return MStatus::kFailure; 505 | } 506 | 507 | if (isInfluenceSymmetryFlagSet) 508 | { 509 | status = validateInfluenceSymmetryArgument(); 510 | RETURN_IF_ERROR(status); 511 | } 512 | 513 | return MStatus::kSuccess; 514 | } 515 | 516 | 517 | MStatus PolySkinWeightsCommand::validateInfluenceSymmetryArgument() 518 | { 519 | MStatus status; 520 | 521 | if (isInfluenceSymmetryFlagSet) 522 | { 523 | if (leftInfluencePattern.empty() || rightInfluencePattern.empty()) 524 | { 525 | MString errorMsg("^1s/^2s patterns must not be empty strings."); 526 | 527 | errorMsg.format( 528 | errorMsg, 529 | MString(INFLUENCE_SYMMETRY_LONG_FLAG), 530 | MString(INFLUENCE_SYMMETRY_FLAG) 531 | ); 532 | 533 | MGlobal::displayError(errorMsg); 534 | return MStatus::kFailure; 535 | } 536 | 537 | bool leftPatternIsValid = pystring::index(leftInfluencePattern, string("*")) != -1; 538 | bool rightPatternIsValid = pystring::index(rightInfluencePattern, string("*")) != -1; 539 | 540 | if (!leftPatternIsValid || !rightPatternIsValid) 541 | { 542 | MString errorMsg("^1s/^2s patterns must have at least one '*' wildcard."); 543 | 544 | errorMsg.format( 545 | errorMsg, 546 | MString(INFLUENCE_SYMMETRY_LONG_FLAG), 547 | MString(INFLUENCE_SYMMETRY_FLAG) 548 | ); 549 | 550 | MGlobal::displayError(errorMsg); 551 | return MStatus::kFailure; 552 | } 553 | } 554 | 555 | return MStatus::kSuccess; 556 | } 557 | 558 | 559 | bool PolySkinWeightsCommand::isDeformedBy(MObject &skin, MDagPath &mesh) 560 | { 561 | bool result = false; 562 | 563 | MObjectArray outputGeometry; 564 | MFnSkinCluster(skin).getOutputGeometry(outputGeometry); 565 | 566 | for (uint i = 0; i < outputGeometry.length(); i++) 567 | { 568 | if (mesh.node() == outputGeometry[i]) 569 | { 570 | result = true; 571 | break; 572 | } 573 | } 574 | 575 | return result; 576 | } 577 | 578 | 579 | MStatus PolySkinWeightsCommand::doIt(const MArgList& argList) 580 | { 581 | MStatus status; 582 | MArgDatabase argsData(syntax(), argList, &status); 583 | RETURN_IF_ERROR(status); 584 | 585 | this->isQuery = argsData.isQuery(); 586 | this->isEdit = argsData.isEdit(); 587 | 588 | if (this->isQuery) 589 | { 590 | status = this->parseQueryArguments(argsData); 591 | RETURN_IF_ERROR(status); 592 | 593 | status = this->validateQueryArguments(); 594 | RETURN_IF_ERROR(status); 595 | } else if (this->isEdit) { 596 | status = this->parseEditArguments(argsData); 597 | RETURN_IF_ERROR(status); 598 | 599 | status = this->validateEditArguments(); 600 | RETURN_IF_ERROR(status); 601 | } else { 602 | status = this->parseArguments(argsData); 603 | RETURN_IF_ERROR(status); 604 | status = this->validateArguments(); 605 | RETURN_IF_ERROR(status); 606 | } 607 | 608 | return this->redoIt(); 609 | } 610 | 611 | 612 | MStatus PolySkinWeightsCommand::redoIt() 613 | { 614 | if (this->isQuery) 615 | { 616 | return this->queryPolySkinWeights(); 617 | } else if (this->isEdit) { 618 | return this->editPolySkinWeights(); 619 | } else { 620 | return this->copyPolySkinWeights(); 621 | } 622 | } 623 | 624 | 625 | /* Do the query command */ 626 | MStatus PolySkinWeightsCommand::queryPolySkinWeights() 627 | { 628 | MStatus status; 629 | 630 | MFnSkinCluster fnSkin(this->sourceSkin); 631 | MDagPathArray influences; 632 | vector influenceKeys; 633 | 634 | uint numberOfInfluences = fnSkin.influenceObjects(influences); 635 | 636 | this->getInfluenceKeys(fnSkin, influenceKeys); 637 | 638 | unordered_map jointLabels; 639 | getJointLabels(influences, influenceKeys, jointLabels); 640 | 641 | this->makeInfluenceSymmetryTable(influences, influenceKeys, jointLabels); 642 | 643 | MStringArray results; 644 | 645 | unordered_map influencesMap; 646 | 647 | for (uint i = 0; i < numberOfInfluences; i++) 648 | { 649 | influencesMap.emplace(influenceKeys[i], influences[i]); 650 | } 651 | 652 | for (uint i = 0; i < numberOfInfluences; i++) 653 | { 654 | MString pair("^1s:^2s"); 655 | 656 | string lhsKey = influenceKeys[i]; 657 | string rhsKey = influenceSymmetry[lhsKey]; 658 | 659 | MString lhs = influencesMap[lhsKey].partialPathName(); 660 | MString rhs = influencesMap[rhsKey].partialPathName(); 661 | 662 | pair.format(pair, lhs, rhs); 663 | 664 | this->appendToResult(pair); 665 | } 666 | 667 | return MStatus::kSuccess; 668 | } 669 | 670 | 671 | /* Do the edit command */ 672 | MStatus PolySkinWeightsCommand::editPolySkinWeights() 673 | { 674 | MStatus status; 675 | 676 | MFnSkinCluster fnSkin(this->sourceSkin); 677 | MDagPathArray influences; 678 | vector influenceKeys; 679 | 680 | uint numberOfInfluences = fnSkin.influenceObjects(influences); 681 | getInfluenceKeys(fnSkin, influenceKeys); 682 | 683 | unordered_map jointLabels; 684 | unordered_map newJointLabels; 685 | 686 | getJointLabels(influences, influenceKeys, jointLabels); 687 | 688 | for (uint i = 0; i < numberOfInfluences; i++) 689 | { 690 | MString influencePathName = influences[i].partialPathName(); 691 | string influenceName(influencePathName.asChar()); 692 | 693 | if (!influences[i].hasFn(MFn::kJoint)) 694 | { 695 | MString warning("Cannot set '^1s' .side, .type, and .otherType attributes because it is not a joint."); 696 | warning.format(warning, influencePathName); 697 | 698 | MGlobal::displayWarning(warning); 699 | continue; 700 | } 701 | 702 | JointLabel oldJointLabel = getJointLabel(influences[i]); 703 | JointLabel newJointLabel = jointLabels[influenceKeys[i]]; 704 | 705 | oldJointLabels.emplace(influenceKeys[i], oldJointLabel); 706 | newJointLabels.emplace(influenceKeys[i], newJointLabel); 707 | 708 | setJointLabel(influences[i], newJointLabel); 709 | } 710 | 711 | return MStatus::kSuccess; 712 | } 713 | 714 | 715 | /* Do the command */ 716 | MStatus PolySkinWeightsCommand::copyPolySkinWeights() 717 | { 718 | MStatus status; 719 | 720 | MFnSkinCluster fnSourceSkin(this->sourceSkin); 721 | MFnSkinCluster fnDestinationSkin(this->destinationSkin); 722 | 723 | bool destinationIsSource = this->sourceSkin == this->destinationSkin; 724 | 725 | if (!destinationIsSource) 726 | { 727 | status = this->makeInfluencesMatch(fnSourceSkin, fnDestinationSkin); 728 | RETURN_IF_ERROR(status); 729 | } 730 | 731 | MDagPathArray sourceInfluences; 732 | MIntArray sourceInfluenceIndices; 733 | vector sourceInfluenceKeys; 734 | MDoubleArray sourceWeights; 735 | uint numSourceInfluences; 736 | 737 | MDagPathArray destinationInfluences; 738 | MIntArray destinationInfluenceIndices; 739 | vector destinationInfluenceKeys; 740 | MDoubleArray destinationWeights; 741 | uint numDestinationInfluences; 742 | 743 | numSourceInfluences = fnSourceSkin.influenceObjects(sourceInfluences); 744 | this->getInfluenceIndices(fnSourceSkin, sourceInfluenceIndices); 745 | this->getInfluenceKeys(fnSourceSkin, sourceInfluenceKeys); 746 | 747 | int nv = (int) this->numberOfVertices; 748 | getAllVertices(nv, sourceComponents); 749 | 750 | numDestinationInfluences = fnDestinationSkin.influenceObjects(destinationInfluences); 751 | this->getInfluenceIndices(fnDestinationSkin, destinationInfluenceIndices); 752 | this->getInfluenceKeys(fnDestinationSkin, destinationInfluenceKeys); 753 | getAllVertices(nv, destinationComponents); 754 | 755 | unordered_map jointLabels; 756 | getJointLabels(destinationInfluences, destinationInfluenceKeys, jointLabels); 757 | 758 | this->makeInfluenceSymmetryTable(destinationInfluences, destinationInfluenceKeys, jointLabels); 759 | this->setupWeightTables(sourceInfluenceKeys); 760 | 761 | if (!destinationIsSource) 762 | { 763 | this->setupWeightTables(destinationInfluenceKeys); 764 | } 765 | 766 | status = fnSourceSkin.getWeights( 767 | sourceMesh, 768 | sourceComponents, 769 | sourceInfluenceIndices, 770 | sourceWeights 771 | ); 772 | CHECK_MSTATUS_AND_RETURN_IT(status); 773 | 774 | if (destinationIsSource) 775 | { 776 | destinationWeights.copy(sourceWeights); 777 | } else { 778 | status = fnDestinationSkin.getWeights( 779 | destinationMesh, 780 | destinationComponents, 781 | destinationInfluenceIndices, 782 | destinationWeights 783 | ); 784 | CHECK_MSTATUS_AND_RETURN_IT(status); 785 | } 786 | 787 | this->setWeightsTable(this->oldWeights, sourceWeights, sourceInfluenceKeys); 788 | 789 | if (flipWeights) 790 | { 791 | this->flipWeightsTable(destinationInfluenceKeys); 792 | } else if (mirrorWeights) { 793 | this->mirrorWeightsTable(destinationInfluenceKeys); 794 | } else { 795 | this->copyWeightsTable(destinationInfluenceKeys); 796 | } 797 | 798 | this->getWeightsTable(this->newWeights, destinationWeights, destinationInfluenceKeys); 799 | 800 | status = fnDestinationSkin.setWeights( 801 | this->destinationMesh, 802 | this->destinationComponents, 803 | destinationInfluenceIndices, 804 | destinationWeights, 805 | this->normalizeWeights, 806 | &this->oldWeightValues 807 | ); 808 | CHECK_MSTATUS_AND_RETURN_IT(status); 809 | 810 | return MStatus::kSuccess; 811 | } 812 | 813 | 814 | /* Ensures that the destination skin cluster has all the influences present on the source skin cluster. */ 815 | MStatus PolySkinWeightsCommand::makeInfluencesMatch(MFnSkinCluster &fnSourceSkin, MFnSkinCluster &fnDestinationSkin) 816 | { 817 | MStatus status; 818 | 819 | MDagPathArray sourceInfluenceObjects; 820 | MDagPathArray destinationInfluenceObjects; 821 | 822 | uint numberOfSourceInfluences = fnSourceSkin.influenceObjects(sourceInfluenceObjects, &status); 823 | CHECK_MSTATUS_AND_RETURN_IT(status); 824 | 825 | uint numberOfDestinationInfluences = fnDestinationSkin.influenceObjects(destinationInfluenceObjects, &status); 826 | CHECK_MSTATUS_AND_RETURN_IT(status); 827 | 828 | MDagPathArray missingInfluences; 829 | 830 | for (uint i = 0; i < numberOfSourceInfluences; i++) 831 | { 832 | bool isMissingInfluence = true; 833 | 834 | for (uint j = 0; j < numberOfDestinationInfluences; j++) 835 | { 836 | if (sourceInfluenceObjects[i] == destinationInfluenceObjects[j]) 837 | { 838 | isMissingInfluence = false; 839 | break; 840 | } 841 | } 842 | 843 | if (isMissingInfluence) 844 | { 845 | missingInfluences.append(sourceInfluenceObjects[i]); 846 | } 847 | } 848 | 849 | if (missingInfluences.length() != 0) 850 | { 851 | MString destinationSkinName = MFnDependencyNode(this->destinationSkin).name(); 852 | MString addMissingInfluencesCmd("skinCluster -edit -lockWeights false -weight 0.0"); 853 | 854 | for (uint i = 0; i < missingInfluences.length(); i++) 855 | { 856 | MString influenceName = missingInfluences[i].partialPathName(); 857 | 858 | if (missingInfluences[i].hasFn(MFn::kShape)) 859 | { 860 | if (missingInfluences[i].node().hasFn(MFn::kTransform)) 861 | { 862 | missingInfluences[i].extendToShapeDirectlyBelow(0); 863 | } 864 | 865 | MString addMissingGeometryInfluenceCmd( 866 | "skinCluster -edit -lockWeights false -weight 0.0 -addInfluence ^1s -useGeometry -baseShape ^2s -polySmoothness 0 -nurbsSamples 10 -dropoffRate 4 ^3s;" 867 | ); 868 | 869 | addMissingGeometryInfluenceCmd.format( 870 | addMissingGeometryInfluenceCmd, 871 | influenceName, 872 | missingInfluences[i].partialPathName(), 873 | destinationSkinName 874 | ); 875 | 876 | dgModifier.commandToExecute(addMissingGeometryInfluenceCmd); 877 | continue; 878 | } else { 879 | addMissingInfluencesCmd += " -addInfluence " + influenceName; 880 | } 881 | } 882 | 883 | addMissingInfluencesCmd += " " + destinationSkinName + ";"; 884 | dgModifier.commandToExecute(addMissingInfluencesCmd); 885 | 886 | status = dgModifier.doIt(); 887 | CHECK_MSTATUS_AND_RETURN_IT(status); 888 | } 889 | 890 | fnDestinationSkin.setObject(this->destinationSkin); 891 | 892 | return MStatus::kSuccess; 893 | } 894 | 895 | 896 | /* Constructs an influence->influence symmetry map. */ 897 | MStatus PolySkinWeightsCommand::makeInfluenceSymmetryTable( 898 | MDagPathArray &influences, 899 | vector &influenceKeys, 900 | unordered_map &jointLabels 901 | ) { 902 | MStatus status; 903 | 904 | uint numberOfInfluences = influences.length(); 905 | 906 | for (string key : influenceKeys) 907 | { 908 | this->influenceSymmetry.emplace(key, key); 909 | } 910 | 911 | for (uint i = 0; i < numberOfInfluences; i++) 912 | { 913 | string thisInfluence = influenceKeys[i]; 914 | JointLabel thisLabel = jointLabels[thisInfluence]; 915 | 916 | for (uint j = 0; j < numberOfInfluences; j++) 917 | { 918 | bool isTaggedCenter = thisLabel.side == CENTER_SIDE || thisLabel.side == NONE_SIDE; 919 | 920 | if ((i == j) && isTaggedCenter) 921 | { 922 | this->influenceSymmetry[thisInfluence] = thisInfluence; 923 | break; 924 | } else if (i == j) { 925 | continue; 926 | } 927 | 928 | string otherInfluence = influenceKeys[j]; 929 | JointLabel otherLabel = jointLabels[otherInfluence]; 930 | 931 | bool leftToRight = (thisLabel.side == LEFT_SIDE && otherLabel.side == RIGHT_SIDE); 932 | bool rightToLeft = (thisLabel.side == RIGHT_SIDE && otherLabel.side == LEFT_SIDE); 933 | 934 | if (leftToRight || rightToLeft) 935 | { 936 | if (thisLabel.type == OTHER_TYPE && otherLabel.type == OTHER_TYPE) 937 | { 938 | if (thisLabel.otherType == otherLabel.otherType) 939 | { 940 | this->influenceSymmetry[thisInfluence] = otherInfluence; 941 | break; 942 | } 943 | } else if (thisLabel.type == otherLabel.type) { 944 | this->influenceSymmetry[thisInfluence] = otherInfluence; 945 | break; 946 | } 947 | } 948 | } 949 | } 950 | 951 | return MStatus::kSuccess; 952 | } 953 | 954 | 955 | /* Constructs a pair of maps (influence->weights) from the influences of the desination skin cluster. */ 956 | void PolySkinWeightsCommand::setupWeightTables(vector &influenceKeys) 957 | { 958 | for (string ii : influenceKeys) 959 | { 960 | if (oldWeights.find(ii) == oldWeights.end()) 961 | { 962 | oldWeights.emplace(ii, vector(this->numberOfVertices)); 963 | } 964 | 965 | if (newWeights.find(ii) == newWeights.end()) 966 | { 967 | newWeights.emplace(ii, vector(this->numberOfVertices)); 968 | } 969 | } 970 | } 971 | 972 | 973 | /* Copies the weights from the weight array to the weight table. */ 974 | void PolySkinWeightsCommand::setWeightsTable(unordered_map> &weightTable, MDoubleArray &weights, vector &influenceKeys) 975 | { 976 | uint numberOfInfluences = (uint) influenceKeys.size(); 977 | 978 | for (uint j = 0; j < numberOfInfluences; j++) 979 | { 980 | vector &weightList = oldWeights[influenceKeys[j]]; 981 | 982 | for (uint i = 0; i < this->numberOfVertices; i++) 983 | { 984 | uint wi = (i * numberOfInfluences) + j; 985 | weightList[i] = weights[wi]; 986 | } 987 | } 988 | } 989 | 990 | /* Copies the weights from the weight table to the weight array. */ 991 | void PolySkinWeightsCommand::getWeightsTable(unordered_map> &weightTable, MDoubleArray &weights, vector &influenceKeys) 992 | { 993 | uint numberOfInfluences = (uint) influenceKeys.size(); 994 | 995 | weights.setLength(this->numberOfVertices * numberOfInfluences); 996 | 997 | for (uint j = 0; j < numberOfInfluences; j++) 998 | { 999 | vector &weightList = weightTable[influenceKeys[j]]; 1000 | 1001 | for (int &i : selectedVertexIndices) 1002 | { 1003 | uint wi = (i * numberOfInfluences) + j; 1004 | double wt = weightList[i]; 1005 | 1006 | weights.set(wt, wi); 1007 | } 1008 | } 1009 | } 1010 | 1011 | 1012 | /* Returns the physical indices of the skin cluster influences. */ 1013 | MStatus PolySkinWeightsCommand::getInfluenceIndices(MFnSkinCluster &fnSkin, MIntArray &influenceIndices) 1014 | { 1015 | MStatus status; 1016 | 1017 | MDagPathArray influences; 1018 | uint numberOfInfluences = fnSkin.influenceObjects(influences); 1019 | 1020 | influenceIndices.setLength(numberOfInfluences); 1021 | 1022 | for (uint i = 0; i < numberOfInfluences; i++) 1023 | { 1024 | /* 1025 | MFnSkinCluster::indexForInfluenceObject returns the LOGICAL index of an influence. 1026 | MFnSkinCluster::setWeights and MFnSkinCluster::getWeights require the PHYSICAL index of an influence. 1027 | 1028 | This is why we can't have nice things. 1029 | */ 1030 | 1031 | influenceIndices.set((int) i, i); 1032 | } 1033 | 1034 | return MStatus::kSuccess; 1035 | } 1036 | 1037 | /* Returns hash keys for the influences on the skin cluster. */ 1038 | MStatus PolySkinWeightsCommand::getInfluenceKeys(MFnSkinCluster &fnSkin, vector &influenceKeys) 1039 | { 1040 | MStatus status; 1041 | 1042 | MDagPathArray influences; 1043 | uint numberOfInfluences = fnSkin.influenceObjects(influences); 1044 | 1045 | influenceKeys.resize(numberOfInfluences); 1046 | 1047 | for (uint i = 0; i < numberOfInfluences; i++) 1048 | { 1049 | influenceKeys[i] = string(influences[i].fullPathName().asChar()); 1050 | } 1051 | 1052 | return MStatus::kSuccess; 1053 | } 1054 | 1055 | /* Copy the weights from the old weights table to the new weights table */ 1056 | void PolySkinWeightsCommand::copyWeightsTable(vector &influenceKeys) 1057 | { 1058 | for (string &ii : influenceKeys) 1059 | { 1060 | vector &newWeightsList = newWeights[ii]; 1061 | vector &oldWeightsList = oldWeights[ii]; 1062 | 1063 | for (uint i = 0; i < numberOfVertices; i++) 1064 | { 1065 | newWeightsList[i] = oldWeightsList[i]; 1066 | } 1067 | } 1068 | } 1069 | 1070 | 1071 | /* Copy the weights from the old weights table indices to the opposite indices in the new weights table. */ 1072 | void PolySkinWeightsCommand::flipWeightsTable(vector &influenceKeys) 1073 | { 1074 | vector vertexSymmetry; 1075 | 1076 | MFnDependencyNode fnNode(this->polySymmetryData); 1077 | PolySymmetryNode::getValues(fnNode, VERTEX_SYMMETRY, vertexSymmetry); 1078 | 1079 | for (string &ii : influenceKeys) 1080 | { 1081 | string &oi = influenceSymmetry[ii]; 1082 | 1083 | vector &newWeightsList = newWeights[ii]; 1084 | vector &oldWeightsList = oldWeights[oi]; 1085 | 1086 | for (int &i : selectedVertexIndices) 1087 | { 1088 | int &o = vertexSymmetry[i]; 1089 | 1090 | newWeightsList[i] = oldWeightsList[o]; 1091 | newWeightsList[o] = oldWeightsList[i]; 1092 | } 1093 | } 1094 | } 1095 | 1096 | 1097 | /* Copy the weights from the old weights table indices to the same and opposite indices in the new weights table. */ 1098 | void PolySkinWeightsCommand::mirrorWeightsTable(vector &influenceKeys) 1099 | { 1100 | vector vertexSymmetry; 1101 | vector vertexSides; 1102 | 1103 | MFnDependencyNode fnNode(this->polySymmetryData); 1104 | PolySymmetryNode::getValues(fnNode, VERTEX_SYMMETRY, vertexSymmetry); 1105 | PolySymmetryNode::getValues(fnNode, VERTEX_SIDES, vertexSides); 1106 | 1107 | vector leftVertexMasks(numberOfVertices); 1108 | vector rightVertexMasks(numberOfVertices); 1109 | 1110 | for (int &i : selectedVertexIndices) 1111 | { 1112 | leftVertexMasks[i] = (vertexSides[i] == CENTER_SIDE) | (vertexSides[i] == direction); 1113 | rightVertexMasks[i] = 1 - leftVertexMasks[i]; 1114 | } 1115 | 1116 | for (string &ii : influenceKeys) 1117 | { 1118 | string &oi = influenceSymmetry[ii]; 1119 | 1120 | vector &newInfluenceWeights = newWeights[ii]; 1121 | vector &oldInfluenceAWeights = oldWeights[ii]; 1122 | vector &oldInfluenceBWeights = oldWeights[oi]; 1123 | 1124 | for (int &i : selectedVertexIndices) 1125 | { 1126 | int &o = vertexSymmetry[i]; 1127 | newInfluenceWeights[i] = (leftVertexMasks[i] * oldInfluenceAWeights[i]) + (rightVertexMasks[i] * oldInfluenceBWeights[o]); 1128 | } 1129 | } 1130 | } 1131 | 1132 | 1133 | /* Populates the jointLabels map. */ 1134 | void PolySkinWeightsCommand::getJointLabels(MDagPathArray &influences, vector &influenceKeys, unordered_map &jointLabels) 1135 | { 1136 | uint numberOfInfluences = influences.length(); 1137 | 1138 | if (this->isInfluenceSymmetryFlagSet) 1139 | { 1140 | string wildcard = string("*"); 1141 | string regexAny = string("(.*)"); 1142 | 1143 | string leftRegexPattern = pystring::replace(leftInfluencePattern, wildcard, regexAny); 1144 | string rightRegexPattern = pystring::replace(rightInfluencePattern, wildcard, regexAny); 1145 | 1146 | regex leftRegex(leftRegexPattern); 1147 | regex rightRegex(rightRegexPattern); 1148 | 1149 | for (uint i = 0; i < numberOfInfluences; i++) 1150 | { 1151 | MString influencePathName = influences[i].partialPathName(); 1152 | string influenceName(influencePathName.asChar()); 1153 | 1154 | JointLabel newJointLabel; 1155 | 1156 | smatch leftMatches; 1157 | smatch rightMatches; 1158 | 1159 | bool isLeft = regex_match(influenceName, leftMatches, leftRegex); 1160 | bool isRight = regex_match(influenceName, rightMatches, rightRegex); 1161 | 1162 | if (isLeft) 1163 | { 1164 | stringstream ss; 1165 | 1166 | for (int i = 1; i < leftMatches.size(); i++) 1167 | { 1168 | ss << leftMatches[i].str(); 1169 | } 1170 | 1171 | influenceName = ss.str(); 1172 | 1173 | newJointLabel.side = LEFT_SIDE; 1174 | } else if (isRight) { 1175 | stringstream ss; 1176 | 1177 | for (int i = 1; i < rightMatches.size(); i++) 1178 | { 1179 | ss << rightMatches[i].str(); 1180 | } 1181 | 1182 | influenceName = ss.str(); 1183 | 1184 | newJointLabel.side = RIGHT_SIDE; 1185 | } else { 1186 | newJointLabel.side = CENTER_SIDE; 1187 | } 1188 | 1189 | newJointLabel.type = OTHER_TYPE; 1190 | newJointLabel.otherType = MString(influenceName.c_str()); 1191 | 1192 | jointLabels.emplace(influenceKeys[i], newJointLabel); 1193 | } 1194 | } else { 1195 | for (uint i = 0; i < numberOfInfluences; i++) 1196 | { 1197 | JointLabel lbl = this->getJointLabel(influences[i]); 1198 | 1199 | jointLabels.emplace(influenceKeys[i], lbl); 1200 | } 1201 | } 1202 | } 1203 | 1204 | 1205 | /* Return the side, type, and otherType values for the given influence. */ 1206 | JointLabel PolySkinWeightsCommand::getJointLabel(MDagPath &influence) 1207 | { 1208 | MStatus status; 1209 | JointLabel result; 1210 | 1211 | MFnDependencyNode fnNode(influence.node()); 1212 | 1213 | MPlug sidePlug = fnNode.findPlug("side", false, &status); 1214 | if (status) { result.side = sidePlug.asInt(); } 1215 | CHECK_MSTATUS(status); 1216 | 1217 | MPlug typePlug = fnNode.findPlug("type", false, &status); 1218 | if (status) { result.type = typePlug.asInt(); } 1219 | CHECK_MSTATUS(status); 1220 | 1221 | MPlug otherTypePlug = fnNode.findPlug("otherType", false, &status); 1222 | if (status && result.type == OTHER_TYPE) { result.otherType = otherTypePlug.asString(); } 1223 | CHECK_MSTATUS(status); 1224 | 1225 | return result; 1226 | } 1227 | 1228 | 1229 | /* Set the side, type, and otherType attributes to the values in `jointLabel`. */ 1230 | MStatus PolySkinWeightsCommand::setJointLabel(MDagPath &influence, JointLabel &jointLabel) 1231 | { 1232 | MStatus status; 1233 | 1234 | MFnDependencyNode fnNode(influence.node()); 1235 | 1236 | MPlug sidePlug = fnNode.findPlug("side", false, &status); 1237 | if (status) 1238 | { 1239 | status = sidePlug.setValue(jointLabel.side); 1240 | CHECK_MSTATUS_AND_RETURN_IT(status); 1241 | } else { 1242 | CHECK_MSTATUS(status); 1243 | } 1244 | 1245 | MPlug typePlug = fnNode.findPlug("type", false, &status); 1246 | if (status) 1247 | { 1248 | status = typePlug.setValue(jointLabel.type); 1249 | CHECK_MSTATUS_AND_RETURN_IT(status); 1250 | } else { 1251 | CHECK_MSTATUS(status); 1252 | } 1253 | 1254 | MPlug otherTypePlug = fnNode.findPlug("otherType", false, &status); 1255 | if (status) 1256 | { 1257 | status = otherTypePlug.setValue(jointLabel.otherType); 1258 | CHECK_MSTATUS_AND_RETURN_IT(status); 1259 | } else { 1260 | CHECK_MSTATUS(status); 1261 | } 1262 | 1263 | return MStatus::kSuccess; 1264 | } 1265 | 1266 | 1267 | MStatus PolySkinWeightsCommand::undoIt() 1268 | { 1269 | if (this->isEdit) 1270 | { 1271 | return this->undoEditPolySkinWeights(); 1272 | } else { 1273 | return this->undoCopyPolySkinWeights(); 1274 | } 1275 | } 1276 | 1277 | 1278 | /* Revert changes to the skinCluster influences, weightList. */ 1279 | MStatus PolySkinWeightsCommand::undoCopyPolySkinWeights() 1280 | { 1281 | MStatus status; 1282 | 1283 | MFnSkinCluster fnSkin(this->destinationSkin); 1284 | 1285 | MIntArray influenceIndices; 1286 | this->getInfluenceIndices(fnSkin, influenceIndices); 1287 | 1288 | status = fnSkin.setWeights( 1289 | this->destinationMesh, 1290 | this->destinationComponents, 1291 | influenceIndices, 1292 | this->oldWeightValues, 1293 | true 1294 | ); 1295 | 1296 | CHECK_MSTATUS_AND_RETURN_IT(status); 1297 | 1298 | status = dgModifier.undoIt(); 1299 | 1300 | CHECK_MSTATUS_AND_RETURN_IT(status); 1301 | 1302 | return MStatus::kSuccess; 1303 | } 1304 | 1305 | 1306 | /* Revert changes to the side/type/otherType attributes on the skinCluster influences. */ 1307 | MStatus PolySkinWeightsCommand::undoEditPolySkinWeights() 1308 | { 1309 | MStatus status; 1310 | 1311 | MFnSkinCluster fnSkin(this->sourceSkin); 1312 | 1313 | MDagPathArray influences; 1314 | vector influenceKeys; 1315 | 1316 | uint numberOfInfluences = fnSkin.influenceObjects(influences); 1317 | getInfluenceKeys(fnSkin, influenceKeys); 1318 | 1319 | for (uint i = 0; i < numberOfInfluences; i++) 1320 | { 1321 | auto got = oldJointLabels.find(influenceKeys[i]); 1322 | 1323 | if (got != oldJointLabels.end()) 1324 | { 1325 | setJointLabel(influences[i], oldJointLabels[influenceKeys[i]]); 1326 | } 1327 | } 1328 | 1329 | return MStatus::kSuccess; 1330 | } 1331 | 1332 | 1333 | bool PolySkinWeightsCommand::isUndoable() const 1334 | { 1335 | return !this->isQuery; 1336 | } --------------------------------------------------------------------------------