diff --git a/.gitignore b/.gitignore index e257658..3e0774e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,33 @@ -# ---> C++ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers +*.un~ *.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - +*.txt +tags +*.pyc +*.attr +*.DS_Store +.clang_complete +tags +ctags +build/ +*/vc/ +vc/ +*/xcode/ +xcode/ +*.sublime-project +*.sublime-workspace +uvChecker/.idea/ +uvChecker/cmake-build-debug/ +modules/ +plug-ins/ +.idea/ +.idea +cmake-build-debug/ +cmake-build-release/ +cmake-build-maya/ +cmake-build-maya_debug +cmake-build-maya_release +!CMakeLists.txt +.clang +sublimeBuild.bat +*ccls* +settings.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e04bb0e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,113 @@ +cmake_minimum_required(VERSION 3.8) +project(CheckTools CXX) + +include(CMakeParseArguments) + +if(NOT MAYA_ROOT_DIR) + message(FATAL_ERROR "MAYA_ROOT_DIR must be set!") +else() + message(STATUS "Using MAYA_ROOT_DIR ${MAYA_ROOT_DIR}") +endif() + +add_library(Maya INTERFACE) +target_compile_features(Maya INTERFACE cxx_std_11) +set_target_properties(Maya PROPERTIES + INTERFACE_POSITION_INDEPENDENT_CODE ON + ) +target_compile_definitions(Maya INTERFACE + REQUIRE_IOSTREAM + _BOOL + ) +target_compile_options(Maya INTERFACE + $<$:pthread -Wall -Wextra -Wconversion -Wsign-conversion -pedantic> + $<$:/W4> + ) +# OS specific +if (WIN32) + target_compile_definitions(Maya INTERFACE NT_PLUGIN) + set(MAYA_INCLUDE_DIR ${MAYA_ROOT_DIR}/include) + set(MAYA_LIB_DIR ${MAYA_ROOT_DIR}/lib) + set(MAYA_PLUGIN_SUFFIX ".mll") +elseif (APPLE) + target_compile_definitions(Maya INTERFACE OSMac_) + set(MAYA_INCLUDE_DIR ${MAYA_ROOT_DIR}/include) + set(MAYA_LIB_DIR ${MAYA_ROOT_DIR}/Maya.app/Contents/MacOS) + set(MAYA_PLUGIN_SUFFIX ".bundle") +elseif (UNIX) + target_compile_definitions(Maya INTERFACE LINUX) + set(MAYA_INCLUDE_DIR ${MAYA_ROOT_DIR}/include) + set(MAYA_LIB_DIR ${MAYA_ROOT_DIR}/lib) + set(MAYA_PLUGIN_SUFFIX ".so") +else() + message(FATAL_ERROR "Operating system not supported") +endif() + +# find MPxCommand header +find_path(MPX_COMMAND_HEADER MPxCommand.h + PATH ${MAYA_INCLUDE_DIR}/maya + NO_DEFAULT_PATH + ) +if(MPX_COMMAND_HEADER) + message(STATUS "Header: MPxCommand.h - FOUND") +else() + message(FATAL_ERROR "Header: MPxCommand.h - NOT FOUND") +endif() + +target_include_directories(Maya SYSTEM INTERFACE ${MAYA_INCLUDE_DIR}) + +# find libs +set(MAYA_LIBS + Foundation + OpenMaya + OpenMayaAnim + OpenMayaFX + OpenMayaRender + OpenMayaUI +) + +foreach(MAYA_LIB ${MAYA_LIBS}) + find_library(LIB-${MAYA_LIB} ${MAYA_LIB} + PATH ${MAYA_LIB_DIR} + NO_DEFAULT_PATH) + if(LIB-${MAYA_LIB}) + message(STATUS "Library: ${MAYA_LIB} - FOUND") + else() + message(FATAL_ERROR "Library: ${MAYA_LIB} - NOT FOUND") + endif() + + target_link_libraries(Maya INTERFACE ${LIB-${MAYA_LIB}}) +endforeach() + +# add_maya_library +function(add_maya_library) + set(opt) + set(one NAME) + set(mul PRIVATE_SOURCE PUBLIC_SOURCE) + cmake_parse_arguments(ADD_MAYA_LIB "${opt}" "${one}" "${mul}" ${ARGN}) + + add_library(${ADD_MAYA_LIB_NAME} SHARED "") + target_link_libraries(${ADD_MAYA_LIB_NAME} PRIVATE Maya) + set_target_properties(${ADD_MAYA_LIB_NAME} PROPERTIES + PREFIX "" + SUFFIX ${MAYA_PLUGIN_SUFFIX} + $<$: LINK_FLAGS /export:initializePlugin /export:uninitializePlugin> + ) + target_sources(${ADD_MAYA_LIB_NAME} + PUBLIC + ${ADD_MAYA_LIB_PUBLIC_SOURCE} + PRIVATE + ${ADD_MAYA_LIB_PRIVATE_SOURCE} + ) +endfunction(add_maya_library) + +add_subdirectory(meshChecker) +add_subdirectory(uvChecker) +add_subdirectory(uvOverlapChecker) + + +if(MAYA_VERSION) + message(STATUS "MAYA_VERSION : ${MAYA_VERSION}") +else() + message(STATUS "MAYA_VERSION not set for install directory") +endif() +set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..874922c --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Michi Inoue + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 1139135..6cf384c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,44 @@ # CheckTools +Mesh/UV check commands for maya +## [meshChecker](https://github.com/minoue/CheckTools/blob/master/meshChecker/) +Mesh/Topology checker + +## [uvChecker](https://github.com/minoue/CheckTools/blob/master/uvChecker/) +General UV checker + +## [findUvOverlaps](https://github.com/minoue/CheckTools/blob/master/uvOverlapChecker/) +UV overlap checker based on the Bentley–Ottmann algorithm + + ⚠️ **Warning** ⚠️ +* Be sure to check if a mesh has no **unassigned UVs**, otherwise maya clashes. + +## GUI + +[ModelCheckerForMaya](https://github.com/minoue/ModelCheckerForMaya) + +## Build + +### Build requirements +C++11 + +### Linux/MacOS +``` +>git clone https://github.com/minoue/CheckTools +>cd CheckTools +>mkdir build +>cd build +>cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DMAYA_VERSION=2020 -DMAYA_ROOT_DIR=path_to_maya_directory ../ +>cmake --build . --config Release --target install +``` + +### Windows +eg. VS2019 and Maya2020 +``` +>git clone https://github.com/minoue/CheckTools +>cd CheckTools +>mkdir build +>cd build +>cmake -G "Visual Studio 16 2019" -DMAYA_VERSION=2020 -DMAYA_ROOT_DIR="C:\Program Files\Autodesk\Maya2020" ../ +>cmake --build . --config Release --target install +``` diff --git a/images/UVsInNegative.png b/images/UVsInNegative.png new file mode 100644 index 0000000..57e33ed Binary files /dev/null and b/images/UVsInNegative.png differ diff --git a/images/concaveUVs.png b/images/concaveUVs.png new file mode 100644 index 0000000..914e19c Binary files /dev/null and b/images/concaveUVs.png differ diff --git a/images/reversedUVs.png b/images/reversedUVs.png new file mode 100644 index 0000000..f279f81 Binary files /dev/null and b/images/reversedUVs.png differ diff --git a/images/udimIntersections.png b/images/udimIntersections.png new file mode 100644 index 0000000..c9c54ac Binary files /dev/null and b/images/udimIntersections.png differ diff --git a/images/unMappedFaces.png b/images/unMappedFaces.png new file mode 100644 index 0000000..ca96ed4 Binary files /dev/null and b/images/unMappedFaces.png differ diff --git a/images/zeroAreaUVFaces.png b/images/zeroAreaUVFaces.png new file mode 100644 index 0000000..b4598d3 Binary files /dev/null and b/images/zeroAreaUVFaces.png differ diff --git a/include/ThreadPool.hpp b/include/ThreadPool.hpp new file mode 100644 index 0000000..4183203 --- /dev/null +++ b/include/ThreadPool.hpp @@ -0,0 +1,98 @@ +#ifndef THREAD_POOL_H +#define THREAD_POOL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ThreadPool { +public: + ThreadPool(size_t); + template + auto enqueue(F&& f, Args&&... args) + -> std::future::type>; + ~ThreadPool(); +private: + // need to keep track of threads so we can join them + std::vector< std::thread > workers; + // the task queue + std::queue< std::function > tasks; + + // synchronization + std::mutex queue_mutex; + std::condition_variable condition; + bool stop; +}; + +// the constructor just launches some amount of workers +inline ThreadPool::ThreadPool(size_t threads) + : stop(false) +{ + for(size_t i = 0;i task; + + { + std::unique_lock lock(this->queue_mutex); + this->condition.wait(lock, + [this]{ return this->stop || !this->tasks.empty(); }); + if(this->stop && this->tasks.empty()) + return; + task = std::move(this->tasks.front()); + this->tasks.pop(); + } + + task(); + } + } + ); +} + +// add new work item to the pool +template +auto ThreadPool::enqueue(F&& f, Args&&... args) + -> std::future::type> +{ + using return_type = typename std::result_of::type; + + auto task = std::make_shared< std::packaged_task >( + std::bind(std::forward(f), std::forward(args)...) + ); + + std::future res = task->get_future(); + { + std::unique_lock lock(queue_mutex); + + // don't allow enqueueing after stopping the pool + if(stop) + throw std::runtime_error("enqueue on stopped ThreadPool"); + + tasks.emplace([task](){ (*task)(); }); + } + condition.notify_one(); + return res; +} + +// the destructor joins all threads +inline ThreadPool::~ThreadPool() +{ + { + std::unique_lock lock(queue_mutex); + stop = true; + } + condition.notify_all(); + for(std::thread &worker: workers) + worker.join(); +} + +#endif diff --git a/include/utils.hpp b/include/utils.hpp new file mode 100644 index 0000000..7181ddb --- /dev/null +++ b/include/utils.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#include +#include +#include + + +inline float getTriangleArea(float Ax, float Ay, float Bx, float By, float Cx, float Cy) +{ + return ((Ax * (By - Cy)) + (Bx * (Cy - Ay)) + (Cx * (Ay - By))) * 0.5F; +} + +enum class ResultType { + Face, + Vertex, + Edge, + UV +}; + +inline void createResultString(const MDagPath& dagPath, ResultType type, int index, std::string& outPath) +{ + outPath = std::string(dagPath.fullPathName().asChar()); + + switch (type) { + case ResultType::Face: { + outPath += ".f[" + std::to_string(index) + "]"; + break; + } + case ResultType::Vertex: { + outPath += ".vtx[" + std::to_string(index) + "]"; + break; + } + case ResultType::Edge: { + outPath += ".e[" + std::to_string(index) + "]"; + break; + } + case ResultType::UV: { + outPath += ".map[" + std::to_string(index) + "]"; + break; + } + } +} + +void buildHierarchy(const MDagPath& path, std::vector& result) +{ + + MString name; + + MItDag dagIter; + for (dagIter.reset(path, MItDag::kDepthFirst); !dagIter.isDone(); dagIter.next()) { + MObject obj = dagIter.currentItem(); + + if (obj.apiType() == MFn::kMesh) { + name = dagIter.fullPathName(); + result.push_back(name.asChar()); + } + } +} \ No newline at end of file diff --git a/meshChecker/CMakeLists.txt b/meshChecker/CMakeLists.txt new file mode 100644 index 0000000..9fc11f4 --- /dev/null +++ b/meshChecker/CMakeLists.txt @@ -0,0 +1,15 @@ +project(meshChecker CXX) + +add_maya_library(NAME ${PROJECT_NAME} + PRIVATE_SOURCE + src/meshChecker.cpp + src/meshChecker.hpp + ) + +if (WIN32) + set(MAYA_TARGET_TYPE RUNTIME) +else () + set(MAYA_TARGET_TYPE LIBRARY) +endif() +set(INSTALL_DIR ../plug-ins/${MAYA_VERSION}) +install(TARGETS ${PROJECT_NAME} ${MAYA_TARGET_TYPE} DESTINATION ${INSTALL_DIR}) \ No newline at end of file diff --git a/meshChecker/README.md b/meshChecker/README.md new file mode 100644 index 0000000..d9e0587 --- /dev/null +++ b/meshChecker/README.md @@ -0,0 +1,35 @@ +# MeshChecker +Mesh/Topology checker for my own + +## Check numbers +0. Triangles +1. Ngons +2. Non-manifold edges +3. Lamina faces +4. Bi-valent faces +5. Zero area faces +6. Mesh border edges +7. Crease edges +8. Zero length edges +9. Vertex pnts attributes +10. Empty geometry (geo with 0 vertices) +11. Instance shpaes +12. Channel connections + +## Flags +| Longname | Shortname | Argument types | Default | Properties | +|:---------|----------:|:--------------:|:-------:|:----------:| +|check|c|int||C| +|maxFaceaArea|mfa|float|0.00001|C| +|minEdgeLength|mel|float|0.000001|C| +|doFix|fix|bool|false|c| + +* 'fix' flag can be used for 'vertex pnts attribute' check + +## Example +```python +from maya import cmds +e = cmds.checkMesh("|pSphere1", c=0) +print e +[u'|pSphere1.f[360]', u'|pSphere1.f[361]', u'|pSphere1.f[362]', u'|pSphere1.f[363]', u'|pSphere1.f[364]', u'|pSphere1.f[365]', u'|pSphere1.f[366]', u'|pSphere1.f[367]', u'|pSphere1.f[368]', u'|pSphere1.f[369]', u'|pSphere1.f[370]', u'|pSphere1.f[371]', u'|pSphere1.f[372]', u'|pSphere1.f[373]', u'|pSphere1.f[374]', u'|pSphere1.f[375]', u'|pSphere1.f[376]', u'|pSphere1.f[377]', u'|pSphere1.f[378]', u'|pSphere1.f[379]', u'|pSphere1.f[380]', u'|pSphere1.f[381]', u'|pSphere1.f[382]', u'|pSphere1.f[383]', u'|pSphere1.f[384]', u'|pSphere1.f[385]', u'|pSphere1.f[386]', u'|pSphere1.f[387]', u'|pSphere1.f[388]', u'|pSphere1.f[389]', u'|pSphere1.f[390]', u'|pSphere1.f[391]', u'|pSphere1.f[392]', u'|pSphere1.f[393]', u'|pSphere1.f[394]', u'|pSphere1.f[395]', u'|pSphere1.f[396]', u'|pSphere1.f[397]', u'|pSphere1.f[398]', u'|pSphere1.f[399]'] +``` diff --git a/meshChecker/src/meshChecker.cpp b/meshChecker/src/meshChecker.cpp new file mode 100644 index 0000000..00f1192 --- /dev/null +++ b/meshChecker/src/meshChecker.cpp @@ -0,0 +1,723 @@ +#include "meshChecker.hpp" +#include "../../include/ThreadPool.hpp" +#include "../../include/utils.hpp" +#include "maya/MApiNamespace.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static const char* const pluginCommandName = "checkMesh"; +static const char* const pluginVersion = "2.3.0"; +static const char* const pluginAuthor = "Michi Inoue"; + +namespace { + +std::vector findTriangles(std::vector* paths) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + std::vector result; + std::string errorPath; + + unsigned int length = list.length(); + + MDagPath dagPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + MFnMesh mesh(dagPath); + int numPoly = mesh.numPolygons(); + + for (int j = 0; j < numPoly; j++) { + if (mesh.polygonVertexCount(j) == 3) { + createResultString(dagPath, ResultType::Face, j, errorPath); + result.push_back(errorPath); + } + } + } + return result; +} + +std::vector findNgons(std::vector* paths) +{ + + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + std::vector result; + std::string errorPath; + + unsigned int length = list.length(); + + MDagPath dagPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + MFnMesh mesh(dagPath); + int numPoly = mesh.numPolygons(); + + for (int i = 0; i < numPoly; i++) { + if (mesh.polygonVertexCount(i) >= 5) { + createResultString(dagPath, ResultType::Face, i, errorPath); + result.push_back(errorPath); + } + } + } + return result; +} + +std::vector findNonManifoldEdges(std::vector* paths) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + std::vector result; + std::string errorPath; + + unsigned int length = list.length(); + + MDagPath dagPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + + for (MItMeshEdge edgeIter(dagPath); !edgeIter.isDone(); edgeIter.next()) { + int face_count; + edgeIter.numConnectedFaces(face_count); + if (face_count > 2) { + createResultString(dagPath, ResultType::Edge, edgeIter.index(), errorPath); + result.push_back(errorPath); + } + } + } + return result; +} + +std::vector findLaminaFaces(std::vector* paths) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + std::vector result; + std::string errorPath; + + unsigned int length = list.length(); + + MDagPath dagPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + + for (MItMeshPolygon polyIter(dagPath); !polyIter.isDone(); polyIter.next()) { + if (polyIter.isLamina()) { + createResultString(dagPath, ResultType::Face, static_cast(polyIter.index()), errorPath); + result.push_back(errorPath); + } + } + } + return result; +} + +std::vector findBiValentFaces(std::vector* paths) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + std::vector result; + std::string errorPath; + + unsigned int length = list.length(); + + MDagPath dagPath; + + MIntArray connectedFaces; + MIntArray connectedEdges; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + + for (MItMeshVertex vtxIter(dagPath); !vtxIter.isDone(); vtxIter.next()) { + vtxIter.getConnectedFaces(connectedFaces); + vtxIter.getConnectedEdges(connectedEdges); + + if (connectedFaces.length() == 2 && connectedEdges.length() == 2) { + createResultString(dagPath, ResultType::Vertex, vtxIter.index(), errorPath); + result.push_back(errorPath); + } + } + } + return result; +} + +std::vector findZeroAreaFaces(std::vector* paths, double maxFaceArea) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + std::vector result; + std::string errorPath; + + unsigned int length = list.length(); + + MDagPath dagPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + + for (MItMeshPolygon polyIter(dagPath); !polyIter.isDone(); polyIter.next()) { + double area; + polyIter.getArea(area); + if (area < maxFaceArea) { + createResultString(dagPath, ResultType::Face, static_cast(polyIter.index()), errorPath); + result.push_back(errorPath); + } + } + } + return result; +} + +std::vector findMeshBorderEdges(std::vector* paths) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + std::vector result; + std::string errorPath; + + unsigned int length = list.length(); + + MDagPath dagPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + + for (MItMeshEdge edgeIter(dagPath); !edgeIter.isDone(); edgeIter.next()) { + if (edgeIter.onBoundary()) { + createResultString(dagPath, ResultType::Edge, edgeIter.index(), errorPath); + result.push_back(errorPath); + } + } + } + return result; +} + +std::vector findCreaseEdges(std::vector* paths) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + unsigned int length = list.length(); + + std::vector result; + std::string errorPath; + + MDagPath dagPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + MFnMesh mesh(dagPath); + + MUintArray edgeIds; + MDoubleArray creaseData; + mesh.getCreaseEdges(edgeIds, creaseData); + + unsigned int edgeIdLength = edgeIds.length(); + + for (unsigned int j = 0; j < edgeIdLength; j++) { + createResultString(dagPath, ResultType::Edge, static_cast(edgeIds[j]), errorPath); + result.push_back(errorPath); + } + } + return result; +} + +std::vector findZeroLengthEdges(std::vector* paths, double minEdgeLength) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + std::vector result; + std::string errorPath; + + unsigned int length = list.length(); + + MDagPath dagPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + for (MItMeshEdge edgeIter(dagPath); !edgeIter.isDone(); edgeIter.next()) { + double length; + edgeIter.getLength(length); + if (length < minEdgeLength) { + createResultString(dagPath, ResultType::Edge, static_cast(edgeIter.index()), errorPath); + result.push_back(errorPath); + } + } + } + return result; +} + +std::vector hasVertexPntsAttr(std::vector* paths) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + std::vector result; + + unsigned int length = list.length(); + + MDagPath dagPath; + + MStatus status; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + dagPath.extendToShape(); + MFnDagNode dagNode(dagPath); + MFnMesh mesh(dagPath); + MPlug pntsArray = mesh.findPlug("pnts", false); + MDataHandle dataHandle = pntsArray.asMDataHandle(); + MArrayDataHandle arrayDataHandle(dataHandle); + MDataHandle outputHandle; + + unsigned int numElements = arrayDataHandle.elementCount(); + + if (numElements == 0) { + continue; + } + + while (true) { + outputHandle = arrayDataHandle.outputValue(); + + const float3& xyz = outputHandle.asFloat3(); + + if (xyz[0] != 0.0) { + result.push_back(dagPath.fullPathName().asChar()); + break; + } + if (xyz[1] != 0.0) { + result.push_back(dagPath.fullPathName().asChar()); + break; + } + if (xyz[2] != 0.0) { + result.push_back(dagPath.fullPathName().asChar()); + break; + } + + // end of iterator + status = arrayDataHandle.next(); + if (status != MS::kSuccess) { + break; + } + } + pntsArray.destructHandle(dataHandle); + } + return result; +} + +std::vector isEmptyGeometry(std::vector* paths) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + std::vector result; + + unsigned int length = list.length(); + MDagPath dagPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + MFnMesh mesh(dagPath); + int numVerts = mesh.numVertices(); + if (numVerts == 0) { + result.push_back(dagPath.fullPathName().asChar()); + } + } + return result; +} + +std::vector findUnusedVertices(std::vector* paths) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + std::vector result; + std::string errorPath; + + unsigned int length = list.length(); + MDagPath dagPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + int edgeCount; + + for (MItMeshVertex vtxIter(dagPath); !vtxIter.isDone(); vtxIter.next()) { + vtxIter.numConnectedEdges(edgeCount); + + if (edgeCount == 0) { + createResultString(dagPath, ResultType::Vertex, vtxIter.index(), errorPath); + result.push_back(errorPath); + } + } + } + return result; +} + +std::vector findInstances(std::vector* paths) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + std::vector result; + std::string errorPath; + + unsigned int length = list.length(); + MDagPath dagPath; + MFnDagNode fnDag; + + for (unsigned int i=0; i < length; i++) { + list.getDagPath(i, dagPath); + fnDag.setObject(dagPath); + if (fnDag.isInstanced()) { + MObject mObj = fnDag.parent(0); + MFnDagNode dataParent(mObj); + MString instanceSource = dataParent.fullPathName(); + dagPath.pop(1); + MString hierarchyParent = dagPath.fullPathName(); + if (hierarchyParent != instanceSource) { + result.push_back(dagPath.fullPathName().asChar()); + } + } + } + + return result; +} + +std::vector findConnections(std::vector* paths) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + std::vector CON_LIST = { + "translateX", + "translateY", + "translateZ", + "rotateX", + "rotateY", + "rotateZ", + "rotateOrder", + "parentInverseMatrix", + "rotatePivot", + "rotatePivotTranslate" + }; + + std::vector result; + + unsigned int length = list.length(); + + MDagPath dagPath; + + for (unsigned int i=0; i < length; i++) { + + MPlugArray plugs; + + list.getDagPath(i, dagPath); + dagPath.pop(1); + MObject mObj = dagPath.node(); + MFnDependencyNode fnDep(mObj); + fnDep.getConnections(plugs); + unsigned int numPlugs = plugs.length(); + for (unsigned int j=0; j(check_value); + + // TODO check if exeds value + } else { + MGlobal::displayError("Check type required."); + return MS::kFailure; + } + + std::vector hierarchy; + buildHierarchy(path, hierarchy); + + // Number of threads to use + size_t numTasks = 8; + + // Split sub-vectors to pass to each thread + std::vector> splitGroups; + + splitGroups.resize(numTasks); + size_t n = hierarchy.size() / numTasks + 1; + size_t idCounter = 0; + for (size_t groupID = 0; groupID < numTasks; groupID++) { + splitGroups[groupID].reserve(n); + } + + for (size_t a = 0; a < numTasks; a++) { + for (size_t b = 0; b < n; b++) { + if (idCounter == hierarchy.size()) { + break; + } + splitGroups[a].emplace_back(hierarchy[idCounter]); + idCounter++; + } + } + + ThreadPool pool(8); + std::vector>> results; + + double maxFaceArea { 0.000001 }; + if (argData.isFlagSet("-maxFaceArea")) + argData.getFlagArgument("-maxFaceArea", 0, maxFaceArea); + + double minEdgeLength = 0.000001; + if (argData.isFlagSet("-minEdgeLength")) + argData.getFlagArgument("-minEdgeLength", 0, minEdgeLength); + + for (size_t i = 0; i < numTasks; i++) { + if (check_type == MeshCheckType::TRIANGLES) { + results.push_back(pool.enqueue(findTriangles, &splitGroups[i])); + } else if (check_type == MeshCheckType::NGONS) { + results.push_back(pool.enqueue(findNgons, &splitGroups[i])); + } else if (check_type == MeshCheckType::NON_MANIFOLD_EDGES) { + results.push_back(pool.enqueue(findNonManifoldEdges, &splitGroups[i])); + } else if (check_type == MeshCheckType::LAMINA_FACES) { + results.push_back(pool.enqueue(findLaminaFaces, &splitGroups[i])); + } else if (check_type == MeshCheckType::BI_VALENT_FACES) { + results.push_back(pool.enqueue(findBiValentFaces, &splitGroups[i])); + } else if (check_type == MeshCheckType::ZERO_AREA_FACES) { + results.push_back(pool.enqueue(findZeroAreaFaces, &splitGroups[i], maxFaceArea)); + } else if (check_type == MeshCheckType::MESH_BORDER) { + results.push_back(pool.enqueue(findMeshBorderEdges, &splitGroups[i])); + } else if (check_type == MeshCheckType::CREASE_EDGE) { + results.push_back(pool.enqueue(findCreaseEdges, &splitGroups[i])); + } else if (check_type == MeshCheckType::ZERO_LENGTH_EDGES) { + results.push_back(pool.enqueue(findZeroLengthEdges, &splitGroups[i], minEdgeLength)); + } else if (check_type == MeshCheckType::UNFROZEN_VERTICES) { + results.push_back(pool.enqueue(hasVertexPntsAttr, &splitGroups[i])); + } else if (check_type == MeshCheckType::EMPTY_GEOMETRY) { + results.push_back(pool.enqueue(isEmptyGeometry, &splitGroups[i])); + } else if (check_type == MeshCheckType::UNUSED_VERTICES) { + results.push_back(pool.enqueue(findUnusedVertices, &splitGroups[i])); + } else if (check_type == MeshCheckType::INSTANCE) { + results.push_back(pool.enqueue(findInstances, &splitGroups[i])); + } else if (check_type == MeshCheckType::CONNECTIONS) { + results.push_back(pool.enqueue(findConnections, &splitGroups[i])); + } else { + MGlobal::displayError("Invalid check number"); + return MS::kFailure; + } + } + + std::vector intermediateResult; + + for (auto&& result : results) { + std::vector temp = result.get(); + for (auto& r : temp) { + intermediateResult.push_back(r); + } + } + + MStringArray outputResult; + + for (std::string& path : intermediateResult) { + outputResult.append(path.c_str()); + } + + setResult(outputResult); + + return redoIt(); +} + +MStatus MeshChecker::redoIt() +{ + return MS::kSuccess; +} + +MStatus MeshChecker::undoIt() +{ + return MS::kSuccess; +} + +bool MeshChecker::isUndoable() const +{ + return false; +} + +void* MeshChecker::creator() +{ + return new MeshChecker; +} + +MSyntax MeshChecker::newSyntax() +{ + MSyntax syntax; + syntax.addArg(MSyntax::kString); + syntax.addFlag("-c", "-check", MSyntax::kUnsigned); + syntax.addFlag("-mfa", "-maxFaceArea", MSyntax::kDouble); + syntax.addFlag("-mel", "-minEdgeLength", MSyntax::kDouble); + syntax.addFlag("-fix", "-doFix", MSyntax::kBoolean); + return syntax; +} + +MStatus initializePlugin(MObject mObj) +{ + MStatus status; + + std::string version_str(pluginVersion); + std::string compile_date_str(__DATE__); + std::string compile_time_str(__TIME__); + std::string version(version_str + " / " + compile_date_str + " / " + compile_time_str); + + MFnPlugin fnPlugin(mObj, pluginAuthor, version.c_str(), "Any"); + + status = fnPlugin.registerCommand(pluginCommandName, MeshChecker::creator, MeshChecker::newSyntax); + if (!status) { + status.perror("registerCommand"); + return status; + } + + return MS::kSuccess; +} + +MStatus uninitializePlugin(MObject mObj) +{ + MStatus status; + + MFnPlugin fnPlugin(mObj); + + status = fnPlugin.deregisterCommand(pluginCommandName); + if (!status) { + status.perror("deregisterCommand"); + return status; + } + + return MS::kSuccess; +} diff --git a/meshChecker/src/meshChecker.hpp b/meshChecker/src/meshChecker.hpp new file mode 100644 index 0000000..8c34892 --- /dev/null +++ b/meshChecker/src/meshChecker.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +enum class MeshCheckType { + TRIANGLES = 0, + NGONS, + NON_MANIFOLD_EDGES, + LAMINA_FACES, + BI_VALENT_FACES, + ZERO_AREA_FACES, + MESH_BORDER, + CREASE_EDGE, + ZERO_LENGTH_EDGES, + UNFROZEN_VERTICES, + EMPTY_GEOMETRY, + UNUSED_VERTICES, + INSTANCE, + CONNECTIONS, + TEST +}; + +class MeshChecker final : public MPxCommand { +public: + static void* creator(); + static MSyntax newSyntax(); + + // command interface + MStatus doIt(const MArgList& argList) final; + MStatus undoIt() final; + MStatus redoIt() final; + bool isUndoable() const final; + +private: + MeshChecker(); +}; diff --git a/uvChecker/CMakeLists.txt b/uvChecker/CMakeLists.txt new file mode 100644 index 0000000..4703894 --- /dev/null +++ b/uvChecker/CMakeLists.txt @@ -0,0 +1,15 @@ +project(uvChecker CXX) + +add_maya_library(NAME ${PROJECT_NAME} + PRIVATE_SOURCE + src/uvChecker.cpp + src/uvChecker.hpp + ) + +if (WIN32) + set(MAYA_TARGET_TYPE RUNTIME) +else () + set(MAYA_TARGET_TYPE LIBRARY) +endif() +set(INSTALL_DIR ../plug-ins/${MAYA_VERSION}) +install(TARGETS ${PROJECT_NAME} ${MAYA_TARGET_TYPE} DESTINATION ${INSTALL_DIR}) \ No newline at end of file diff --git a/uvChecker/README.md b/uvChecker/README.md new file mode 100644 index 0000000..657df0c --- /dev/null +++ b/uvChecker/README.md @@ -0,0 +1,50 @@ +# UVChecker +Find general uv errors + +## Check Numbers + +### 0. Udim border intersections + +![](../images/udimIntersections.png) + +### 1. Unmapped polygon faces + +![](../images/unMappedFaces.png) + +### 2. Zero-area UV faces + +![](../images/zeroAreaUVFaces.png) + +### 3. Unassigned UVs + +![img]() + +### 4. UVs in negative space + +![](../images/UVsInNegative.png) + +### 5. Concave UV faces + +![](../images/concaveUVs.png) + +### 6. Reversed UVs + +![](../images/reversedUVs.png) + +## Flags +| Longname | Shortname | Argument types | Default | Properties | Description | +|:---------|----------:|:--------------:|:-------:|:----------:|:-----------:| +|check|c|integer||C|| +|uvArea|uva|double|0.000001|C|| +|uvSet|us|string|current uv set|C|Set what uv set you want to us| +|maxUvBorderDistance|muvd|double|0.0|C|Ignore UVs close to udims borders for "Udim border intersections" check| +|verbose|v|bool|False|C|| + + +## Example +```python +from maya import cmds +errors = cmds.checkUV("|pSphere1", c=0) +print errors +>>> [u'|pSphere1|pSphereShape1.map[19]', u'|pSphere1|pSphereShape1.map[20]', ...] +``` diff --git a/uvChecker/python/removeUnassginedUVs.py b/uvChecker/python/removeUnassginedUVs.py new file mode 100644 index 0000000..56a6901 --- /dev/null +++ b/uvChecker/python/removeUnassginedUVs.py @@ -0,0 +1,109 @@ +""" +Remove unassigned UVs +""" + +from maya import OpenMaya +from maya import cmds + + +def checkUnassignedUVs(): + + bads = [] + + sel = cmds.ls(sl=True, fl=True, long=True) + + if len(sel) == 0: + cmds.error("Nothing is selected") + return + + root = sel[0] + children = cmds.listRelatives(root, ad=True, type="mesh", fullPath=True) + + sel = OpenMaya.MSelectionList() + for i in children: + sel.add(i) + + # OpenMaya.MGlobal.getActiveSelectionList(sel) + dagPath = OpenMaya.MDagPath() + + for i in range(sel.length()): + + sel.getDagPath(i, dagPath) + + fnMesh = OpenMaya.MFnMesh(dagPath) + numAllUVs = fnMesh.numUVs() + + uvCounts = OpenMaya.MIntArray() + uvIds = OpenMaya.MIntArray() + fnMesh.getAssignedUVs(uvCounts, uvIds) + numAssignedUVs = len(set(uvIds)) + if numAllUVs != numAssignedUVs: + bads.append(dagPath.fullPathName()) + + return bads + + +def removeUnassignedUVs(): + sel = OpenMaya.MSelectionList() + OpenMaya.MGlobal.getActiveSelectionList(sel) + + if sel.length() == 0: + cmds.error('Nothing is selected') + return + + dagPath = OpenMaya.MDagPath() + sel.getDagPath(0, dagPath) + + fnMesh = OpenMaya.MFnMesh(dagPath) + uArray = OpenMaya.MFloatArray() + vArray = OpenMaya.MFloatArray() + fnMesh.getUVs(uArray, vArray) + + uvCounts = OpenMaya.MIntArray() + uvIds = OpenMaya.MIntArray() + fnMesh.getAssignedUVs(uvCounts, uvIds) + + # This is UVs that are actually assgined to the geometry + # So you have to keep UVs of those indices. + # Then you have to remove all non-assgined UVs(what we call ghost UVs) + oldUVIndicesSorted = sorted(list(set(uvIds))) + + # Remap old indices to new clean indices + # eg. [x, x, x, 3, x, x, 6, x, x, 9, x, ....] (x means unassigned UVs to be removed) + # | + # [3, 6, 9, ...] <- Remove Unassgined UVs and pack left UVs + # | + # [0, 1, 2, ...] <- Remap to clean order + + # Create new dict which key is old uv indices and value is new uv indices + # In the above example, { 3:0, 6:1, 9:2, .... } + uvMap = {} + for num, oldUVIndex in enumerate(oldUVIndicesSorted): + uvMap[oldUVIndex] = num + + # Replace old UV indices of each face to new UV indices + newUvIds = OpenMaya.MIntArray() + for i in uvIds: + newUvIds.append(uvMap[i]) + + # Construct u and v arrays using only assigned UVs + newUs = [uArray[i] for i in oldUVIndicesSorted] + newVs = [vArray[i] for i in oldUVIndicesSorted] + + # Convert python new uvArrays from python list to MFloatArray + newUArray = OpenMaya.MFloatArray() + newVArray = OpenMaya.MFloatArray() + for i in newUs: + newUArray.append(i) + for i in newVs: + newVArray.append(i) + + # Clear current uvSet and re-assgin new UV information + fnMesh.clearUVs() + fnMesh.setUVs(newUArray, newVArray) + fnMesh.assignUVs(uvCounts, newUvIds) + fnMesh.updateSurface() + + +if __name__ == "__main__": + removeUnassignedUVs() diff --git a/uvChecker/src/uvChecker.cpp b/uvChecker/src/uvChecker.cpp new file mode 100644 index 0000000..83cff00 --- /dev/null +++ b/uvChecker/src/uvChecker.cpp @@ -0,0 +1,545 @@ +#include "uvChecker.hpp" +#include "../../include/ThreadPool.hpp" +#include "../../include/utils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static const char* const pluginCommandName = "checkUV"; +static const char* const pluginVersion = "2.1.3"; +static const char* const pluginAuthor = "Michi Inoue"; + +namespace { + +std::vector findUdimIntersections(std::vector* paths, const MString uvSet, const double maxUvBorderDistance) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + unsigned int length = list.length(); + + MDagPath dagPath; + MFnMesh mesh; + + std::vector result2; + std::string errorPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + mesh.setObject(dagPath); + std::vector indices; + + for (MItMeshPolygon mItPoly(dagPath); !mItPoly.isDone(); mItPoly.next()) { + int vCount = static_cast(mItPoly.polygonVertexCount()); + int currentUVindex; + int nextUVindex; + float u1, v1, u2, v2; + + for (int j = 0; j < vCount; j++) { + mItPoly.getUVIndex(j, currentUVindex, &uvSet); + + if (j == vCount - 1) { + mItPoly.getUVIndex(0, nextUVindex, &uvSet); + } else { + mItPoly.getUVIndex(j + 1, nextUVindex, &uvSet); + } + + mesh.getUV(currentUVindex, u1, v1, &uvSet); + mesh.getUV(nextUVindex, u2, v2, &uvSet); + + if (floor(u1) == floor(u2) && floor(v1) == floor(v2)) { + } else if ((maxUvBorderDistance == 0.0) + || ((fabs(rint(u1) - fabs(u1)) > maxUvBorderDistance) + && (fabs(rint(v1) - fabs(v1)) > maxUvBorderDistance) + && (fabs(rint(u2) - fabs(u2)) > maxUvBorderDistance) + && (fabs(rint(v2) - fabs(v2)) > maxUvBorderDistance))) { + indices.push_back(currentUVindex); + indices.push_back(nextUVindex); + } + } + } + // Remove duplicate elements + std::sort(indices.begin(), indices.end()); + indices.erase(std::unique(indices.begin(), indices.end()), indices.end()); + + for (auto& index : indices) { + createResultString(dagPath, ResultType::UV, index, errorPath); + result2.push_back(errorPath); + } + } + + return result2; +} + +std::vector findNoUvFaces(std::vector* paths, const MString uvSet) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + unsigned int length = list.length(); + MDagPath dagPath; + bool hasUVs; + std::vector result2; + std::string errorPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + for (MItMeshPolygon itPoly(dagPath); !itPoly.isDone(); itPoly.next()) { + hasUVs = itPoly.hasUVs(uvSet); + if (!hasUVs) { + createResultString(dagPath, ResultType::Face, static_cast(itPoly.index()), errorPath); + result2.push_back(errorPath); + } + } + } + return result2; +} + +std::vector findZeroUvFaces(std::vector* paths, const MString uvSet, const double minUVArea) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + unsigned int length = list.length(); + + MDagPath dagPath; + MFnMesh mesh; + + std::vector result2; + std::string errorPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + mesh.setObject(dagPath); + double area; + bool hasUVs; + for (MItMeshPolygon itPoly(dagPath); !itPoly.isDone(); itPoly.next()) { + hasUVs = itPoly.hasUVs(uvSet); + if (hasUVs) { + itPoly.getUVArea(area, &uvSet); + if (area < minUVArea) { + createResultString(dagPath, ResultType::Face, static_cast(itPoly.index()), errorPath); + result2.push_back(errorPath); + } + } + } + } + return result2; +} + +std::vector hasUnassignedUVs(std::vector* paths, const MString uvSet) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + unsigned int length = list.length(); + + MDagPath dagPath; + MFnMesh mesh; + + std::vector result2; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + mesh.setObject(dagPath); + + int numUVs = mesh.numUVs(uvSet); + MIntArray uvCounts; + MIntArray uvIds; + mesh.getAssignedUVs(uvCounts, uvIds, &uvSet); + unsigned int numUvIds = uvIds.length(); + + std::unordered_set uvIdSet; + for (unsigned int i = 0; i < numUvIds; i++) { + uvIdSet.insert(uvIds[i]); + } + + int numAssignedUVs = static_cast(uvIdSet.size()); + + if (numUVs != numAssignedUVs) { + result2.push_back(dagPath.fullPathName().asChar()); + } + } + return result2; +} + +std::vector findNegativeSpaceUVs(std::vector* paths, const MString uvSet) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + unsigned int length = list.length(); + + MDagPath dagPath; + MFnMesh mesh; + + std::vector result2; + std::string errorPath; + + MFloatArray uArray, vArray; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + mesh.setObject(dagPath); + mesh.getUVs(uArray, vArray, &uvSet); + + int numUVs = mesh.numUVs(uvSet); + + for (int j = 0; j < numUVs; j++) { + float& u = uArray[static_cast(j)]; + if (u < 0.0) { + createResultString(dagPath, ResultType::UV, j, errorPath); + result2.push_back(errorPath); + continue; + } + float& v = vArray[static_cast(j)]; + if (v < 0.0) { + createResultString(dagPath, ResultType::UV, j, errorPath); + result2.push_back(errorPath); + continue; + } + } + } + return result2; +} + +std::vector findConcaveUVs(std::vector* paths, const MString uvSet) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + unsigned int length = list.length(); + + MDagPath dagPath; + + std::vector result2; + std::string errorPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + std::vector indices; + + MPointArray points; + MIntArray vertexList; + + for (MItMeshPolygon itPoly(dagPath); !itPoly.isDone(); itPoly.next()) { + unsigned int polygonIndex = itPoly.index(); + itPoly.getTriangles(points, vertexList); + int numVerts = static_cast(itPoly.polygonVertexCount()); + int p1, p2, p3; + bool isReversed = itPoly.isUVReversed(); + + for (int i = 0; i < numVerts; i++) { + p1 = i; + p2 = i + 1; + p3 = i + 2; + + if (p2 > numVerts - 1) { + p2 -= numVerts; + } + if (p3 > numVerts - 1) { + p3 -= numVerts; + } + + float2 uv1; + float2 uv2; + float2 uv3; + + itPoly.getUV(p1, uv1, &uvSet); + itPoly.getUV(p2, uv2, &uvSet); + itPoly.getUV(p3, uv3, &uvSet); + + float S = getTriangleArea(uv1[0], uv1[1], uv2[0], uv2[1], uv3[0], uv3[1]); + + if (!isReversed) { + if (S <= 0.00000000001) { + indices.push_back(static_cast(polygonIndex)); + } + } else { + if (S >= -0.00000000001) { + indices.push_back(static_cast(polygonIndex)); + } + } + } + } + + for (auto& index : indices) { + createResultString(dagPath, ResultType::Face, index, errorPath); + result2.push_back(errorPath); + } + } + return result2; +} + +std::vector findReversedUVs(std::vector* paths, const MString uvSet) +{ + MSelectionList list; + + for (auto& p : *paths) { + MString mPath(p.c_str()); + list.add(mPath); + } + + unsigned int length = list.length(); + + MDagPath dagPath; + MFnMesh mesh; + + std::vector result2; + std::string errorPath; + + for (unsigned int i = 0; i < length; i++) { + list.getDagPath(i, dagPath); + mesh.setObject(dagPath); + + for (MItMeshPolygon itPoly(dagPath); !itPoly.isDone(); itPoly.next()) { + if (itPoly.isUVReversed(&uvSet)) { + createResultString(dagPath, ResultType::Face, static_cast(itPoly.index()), errorPath); + result2.push_back(errorPath); + } + } + } + return result2; +} + +} // unnamed namespace + +UvChecker::UvChecker() + : verbose(false) + , + // uvSet("map1"), + minUVArea(0.000001) + , maxUvBorderDistance(0.0) +{ +} + +UvChecker::~UvChecker() + = default; + +MSyntax UvChecker::newSyntax() +{ + MSyntax syntax; + syntax.addArg(MSyntax::kString); + syntax.addFlag("-v", "-verbose", MSyntax::kBoolean); + syntax.addFlag("-c", "-check", MSyntax::kUnsigned); + syntax.addFlag("-uva", "-uvArea", MSyntax::kDouble); + syntax.addFlag("-us", "-uvSet", MSyntax::kString); + syntax.addFlag("-muv", "-maxUvBorderDistance", MSyntax::kDouble); + return syntax; +} + +MStatus UvChecker::doIt(const MArgList& args) +{ + MStatus status; + + MSelectionList sel; + + MArgDatabase argData(syntax(), args); + + status = argData.getCommandArgument(0, sel); + + if (status != MS::kSuccess) { + MGlobal::displayError("You have to provide an object path"); + return MStatus::kFailure; + } + + MDagPath path; + sel.getDagPath(0, path); + + // argument parsing + UVCheckType check_type; + + if (argData.isFlagSet("-check")) { + unsigned int check_value; + argData.getFlagArgument("-check", 0, check_value); + + check_type = static_cast(check_value); + + // TODO check if exceeds value + } else { + MGlobal::displayError("Check type required."); + return MS::kFailure; + } + + if (argData.isFlagSet("-verbose")) + argData.getFlagArgument("-verbose", 0, verbose); + + if (argData.isFlagSet("-uvArea")) + argData.getFlagArgument("-uvArea", 0, minUVArea); + + if (argData.isFlagSet("-uvSet")) + argData.getFlagArgument("-uvSet", 0, uvSet); + else + // mesh.getCurrentUVSetName(uvSet); + uvSet = "map1"; + + if (argData.isFlagSet("-maxUvBorderDistance")) + argData.getFlagArgument("-maxUvBorderDistance", 0, maxUvBorderDistance); + + if (verbose) { + MString objectPath = "Selected mesh : " + path.fullPathName(); + MGlobal::displayInfo(objectPath); + } + + std::vector hierarchy; + buildHierarchy(path, hierarchy); + + // Number of threads to use + size_t numTasks = 8; + + // Split sub-vectors to pass to each thread + std::vector> splitGroups; + + splitGroups.resize(numTasks); + size_t n = hierarchy.size() / numTasks + 1; + size_t idCounter = 0; + for (size_t groupID = 0; groupID < numTasks; groupID++) { + splitGroups[groupID].reserve(n); + } + + for (size_t a = 0; a < numTasks; a++) { + for (size_t b = 0; b < n; b++) { + if (idCounter == hierarchy.size()) { + break; + } + splitGroups[a].emplace_back(hierarchy[idCounter]); + idCounter++; + } + } + + ThreadPool pool(8); + std::vector>> results; + + for (size_t i = 0; i < numTasks; i++) { + if (check_type == UVCheckType::UDIM) { + results.push_back(pool.enqueue(findUdimIntersections, &splitGroups[i], uvSet, maxUvBorderDistance)); + } else if (check_type == UVCheckType::HAS_UVS) { + results.push_back(pool.enqueue(findNoUvFaces, &splitGroups[i], uvSet)); + } else if (check_type == UVCheckType::ZERO_AREA) { + results.push_back(pool.enqueue(findZeroUvFaces, &splitGroups[i], uvSet, minUVArea)); + } else if (check_type == UVCheckType::UN_ASSIGNED_UVS) { + results.push_back(pool.enqueue(hasUnassignedUVs, &splitGroups[i], uvSet)); + } else if (check_type == UVCheckType::NEGATIVE_SPACE_UVS) { + results.push_back(pool.enqueue(findNegativeSpaceUVs, &splitGroups[i], uvSet)); + } else if (check_type == UVCheckType::CONCAVE_UVS) { + results.push_back(pool.enqueue(findConcaveUVs, &splitGroups[i], uvSet)); + } else if (check_type == UVCheckType::REVERSED_UVS) { + results.push_back(pool.enqueue(findReversedUVs, &splitGroups[i], uvSet)); + } else { + MGlobal::displayError("Invalid check number"); + return MS::kFailure; + } + } + + std::vector intermediateResult; + + for (auto&& result : results) { + std::vector temp = result.get(); + for (auto& r : temp) { + intermediateResult.push_back(r); + } + } + + MStringArray outputResult; + + for (std::string& path : intermediateResult) { + outputResult.append(path.c_str()); + } + + setResult(outputResult); + + return redoIt(); +} + +MStatus UvChecker::redoIt() +{ + return MS::kSuccess; +} + +MStatus UvChecker::undoIt() +{ + return MS::kSuccess; +} + +bool UvChecker::isUndoable() const +{ + return false; +} + +void* UvChecker::creator() +{ + return new UvChecker; +} + +MStatus initializePlugin(MObject mObj) +{ + MStatus status; + + std::string version_str(pluginVersion); + std::string compile_date_str(__DATE__); + std::string compile_time_str(__TIME__); + std::string version(version_str + " / " + compile_date_str + " / " + compile_time_str); + + MFnPlugin fnPlugin(mObj, pluginAuthor, version.c_str(), "Any"); + + status = fnPlugin.registerCommand(pluginCommandName, UvChecker::creator, UvChecker::newSyntax); + if (!status) { + status.perror("registerCommand"); + return status; + } + + return MS::kSuccess; +} + +MStatus uninitializePlugin(MObject mObj) +{ + MStatus status; + + MFnPlugin fnPlugin(mObj); + status = fnPlugin.deregisterCommand(pluginCommandName); + if (!status) { + status.perror("deregisterCommand"); + return status; + } + + return MS::kSuccess; +} diff --git a/uvChecker/src/uvChecker.hpp b/uvChecker/src/uvChecker.hpp new file mode 100644 index 0000000..d057d63 --- /dev/null +++ b/uvChecker/src/uvChecker.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +enum class UVCheckType { + UDIM = 0, + HAS_UVS, + ZERO_AREA, + UN_ASSIGNED_UVS, + NEGATIVE_SPACE_UVS, + CONCAVE_UVS, + REVERSED_UVS +}; + +class UvChecker final : public MPxCommand { +public: + UvChecker(); + ~UvChecker() final; + + // command interface + MStatus doIt(const MArgList& argList) final; + MStatus undoIt() final; + MStatus redoIt() final; + bool isUndoable() const final; + + static void* creator(); + static MSyntax newSyntax(); + +private: + bool verbose; + double minUVArea; + MString uvSet; + double maxUvBorderDistance; +}; diff --git a/uvOverlapChecker/CMakeLists.txt b/uvOverlapChecker/CMakeLists.txt new file mode 100644 index 0000000..f12d00f --- /dev/null +++ b/uvOverlapChecker/CMakeLists.txt @@ -0,0 +1,34 @@ +project(findUvOverlaps CXX) + +add_maya_library(NAME ${PROJECT_NAME} + PRIVATE_SOURCE + src/findUvOverlaps.cpp + src/findUvOverlaps.hpp + src/bentleyOttmann/bentleyOttmann.cpp + src/bentleyOttmann/bentleyOttmann.hpp + src/bentleyOttmann/event.hpp + src/bentleyOttmann/event.cpp + src/bentleyOttmann/lineSegment.hpp + src/bentleyOttmann/lineSegment.cpp + src/bentleyOttmann/point2D.hpp + src/bentleyOttmann/point2D.cpp + src/bentleyOttmann/vector2D.hpp + src/bentleyOttmann/vector2D.cpp + ) + +if (NOT APPLE) + find_package(OpenMP REQUIRED) + target_link_libraries( + ${PROJECT_NAME} + PRIVATE + OpenMP::OpenMP_CXX + ) +endif() + +if (WIN32) + set(MAYA_TARGET_TYPE RUNTIME) +else () + set(MAYA_TARGET_TYPE LIBRARY) +endif() +set(INSTALL_DIR ../plug-ins/${MAYA_VERSION}) +install(TARGETS ${PROJECT_NAME} ${MAYA_TARGET_TYPE} DESTINATION ${INSTALL_DIR}) \ No newline at end of file diff --git a/uvOverlapChecker/README.md b/uvOverlapChecker/README.md new file mode 100644 index 0000000..094aa5d --- /dev/null +++ b/uvOverlapChecker/README.md @@ -0,0 +1,30 @@ +## FindUvOverlaps +Find overlapped UVs. + +### Flags +| Longname | Shortname | Argument types | Default | Properties | +|:---------|----------:|:--------------:|:-------:|:----------:| +|verbose|v|bool|False|C| + +### Example + +```python +from maya import cmds +r = cmds.findUvOverlaps("|pSphere1") +print r +>>> [u'|pPlane1|pPlaneShape1.map[38]', u'|pPlane1|pPlaneShape1.map[39]', ....] +``` + +For multiple object check, select multiple objects and just run the command without path argument. + +```python +cmds.findUvOverlaps() +``` + +* Single object selected or object path specified as command argment + + Image + +* Multiple object selected + + Image diff --git a/uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.cpp b/uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.cpp new file mode 100644 index 0000000..93f7b7f --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.cpp @@ -0,0 +1,263 @@ +// +// bentleyOttmann.cpp +// bentleyOttmann +// + +#include "bentleyOttmann.hpp" +#include +#include + +BentleyOttmann::BentleyOttmann(std::vector& edgeVector) : edges(edgeVector) +{ +} + +BentleyOttmann::~BentleyOttmann() += default; + +BentleyOttmann BentleyOttmann::operator+(const BentleyOttmann& rhs) const +{ + // Create new BentleyOttmann object + size_t newSize = this->edges.size() + rhs.edges.size(); + std::vector AB; + AB.reserve(newSize); + AB.insert(AB.end(), this->edges.begin(), this->edges.end()); + AB.insert(AB.end(), rhs.edges.begin(), rhs.edges.end()); + + BentleyOttmann newBO(AB); + + return newBO; +} + +void BentleyOttmann::createNewEvent(LineSegment* lineA, LineSegment* lineB) +{ + float x, y; + lineUtils::getIntersectionPoint(*lineA, *lineB, x, y); + Point2D p(x, y, 0); + Event crossEvent(2, lineA, lineB, p); + eventQueue.insert(crossEvent); +} + +void BentleyOttmann::check(std::vector &result) +{ + resultPtr = &result; + + for (auto & edge : edges) { + Event ev1(0, &edge, edge.begin); + Event ev2(1, &edge, edge.end); + eventQueue.insert(ev1); + eventQueue.insert(ev2); + } + + statusPtrQueue.reserve(edges.size()); + + while (true) { + if (eventQueue.empty()) { + break; + } + + Event firstEvent = *eventQueue.begin(); + eventQueue.erase(eventQueue.begin()); + + switch (firstEvent.eventType) { + case Event::BEGIN: + doBegin(firstEvent); + break; + case Event::END: + doEnd(firstEvent); + break; + case Event::CROSS: + doCross(firstEvent); + break; + default: + break; + } + } +} + +bool BentleyOttmann::doBegin(Event& ev) +{ + LineSegment* currentEdgePtr = ev.edgePtrA; + statusPtrQueue.emplace_back(currentEdgePtr); + + if (statusPtrQueue.size() <= 1) { + return false; + } + + // Set crossing point of Y for all edges in the statusQueue and the sweepline + for (auto ePtr : statusPtrQueue) { + if (ePtr->isHorizontal) { + // If the edge is horizontal, y of corrsing point is always y + // as sweepline moves from left to right + ePtr->crossingPointY = ePtr->begin.y; + } else if (ePtr->isVertical) { + // if the edge is vertical, the edge and sweepline are collinear, so + // use y of a mid point instread + float mid = (ePtr->begin.y + ePtr->end.y) * 0.5F; + ePtr->crossingPointY = mid; + } else { + // otherwise, find y value of crossing point of the edge and sweepline + float slope2 = (ePtr->end.y - ePtr->begin.y) / (ePtr->end.x - ePtr->begin.x); + float b2 = ePtr->begin.y - slope2 * ePtr->begin.x; + float y2 = slope2 * ev.sweepline + b2; + ePtr->crossingPointY = y2; + } + } + std::sort(statusPtrQueue.begin(), statusPtrQueue.end(), EdgeCrossingComparator()); + + // StatusQueue was sorted so you have to find the edge added to the queue above and find its index + auto foundIter = std::find(statusPtrQueue.begin(), statusPtrQueue.end(), + currentEdgePtr); + if (foundIter == statusPtrQueue.end()) { + return false; + } + + // Get currentEdge object from the statusQueue after sorted + size_t index = static_cast(std::distance(statusPtrQueue.begin(), foundIter)); + + if (foundIter == statusPtrQueue.begin()) { + LineSegment* targetEdge = statusPtrQueue[index + 1]; + if (*(*foundIter) * *(targetEdge)) { + resultPtr->emplace_back(*(*foundIter)); + resultPtr->emplace_back(*targetEdge); + createNewEvent(currentEdgePtr, targetEdge); + } + } + else if (foundIter == statusPtrQueue.end() - 1) { + LineSegment* targetEdge = statusPtrQueue[index - 1]; + if (*(*foundIter) * *(targetEdge)) { + resultPtr->emplace_back(*(*foundIter)); + resultPtr->emplace_back(*targetEdge); + createNewEvent(currentEdgePtr, targetEdge); + } + } + else { + LineSegment* nextEdgePtr = statusPtrQueue[index + 1]; + LineSegment* previousEdgePtr = statusPtrQueue[index - 1]; + if (*(*foundIter) * *(nextEdgePtr)) { + resultPtr->emplace_back(*(*foundIter)); + resultPtr->emplace_back(*nextEdgePtr); + createNewEvent(currentEdgePtr, nextEdgePtr); + } + if (*(*foundIter) * *(previousEdgePtr)) { + resultPtr->emplace_back(*(*foundIter)); + resultPtr->emplace_back(*previousEdgePtr); + createNewEvent(currentEdgePtr, previousEdgePtr); + } + } + return true; +} + +bool BentleyOttmann::doEnd(Event& ev) +{ + LineSegment* currentEdgePtr = ev.edgePtrA; + auto foundIter = std::find(statusPtrQueue.begin(), statusPtrQueue.end(), currentEdgePtr); + + if (foundIter == statusPtrQueue.end()) { + // if iter not found + std::cout << "Not found" << std::endl; + return false; + } + + if (foundIter == statusPtrQueue.begin() || foundIter == statusPtrQueue.end() - 1) { + + } + else { + size_t index = static_cast(std::distance(statusPtrQueue.begin(), foundIter)); + LineSegment* nextEdgePtr = statusPtrQueue[index + 1]; + LineSegment* previousEdgePtr = statusPtrQueue[index - 1]; + bool isCrossing = (*nextEdgePtr) * (*previousEdgePtr); + if (isCrossing) { + resultPtr->emplace_back(*previousEdgePtr); + resultPtr->emplace_back(*nextEdgePtr); + createNewEvent(nextEdgePtr, previousEdgePtr); + } + } + + // Remove current edge from the statusQueue + statusPtrQueue.erase(foundIter); + + return true; +} + +bool BentleyOttmann::doCross(Event& ev) +{ + if (statusPtrQueue.size() <= 2) { + return false; + } + + LineSegment* edgePtr = ev.edgePtrA; + LineSegment* otherEdgePtr = ev.edgePtrB; + + auto lineAPtrIter = std::find(statusPtrQueue.begin(), statusPtrQueue.end(), + edgePtr); + auto lineBPtrIter = std::find(statusPtrQueue.begin(), statusPtrQueue.end(), + otherEdgePtr); + if (lineAPtrIter == statusPtrQueue.end() || lineBPtrIter == statusPtrQueue.end()) { + return false; + } + + size_t lineAIndex = static_cast(std::distance(statusPtrQueue.begin(), lineAPtrIter)); + size_t lineBIndex = static_cast(std::distance(statusPtrQueue.begin(), lineBPtrIter)); + size_t small, big; + + if (lineAIndex > lineBIndex) { + small = lineBIndex; + big = lineAIndex; + } + else { + small = lineAIndex; + big = lineBIndex; + } + + if (small == 0) { + // Check the first edge and the one after next edge + + // If If the second edge is the last element of the statusQueue, then there is + // no edge to be checked with the first edge + if (statusPtrQueue.size() == static_cast(big) + 1) { + return false; + } + + LineSegment* lineAPtr = statusPtrQueue[small]; + LineSegment* lineBPtr = statusPtrQueue[big + 1]; + bool isCrossing = (*lineAPtr) * (*lineBPtr); + if (isCrossing) { + resultPtr->emplace_back(*lineAPtr); + resultPtr->emplace_back(*lineBPtr); + createNewEvent(lineAPtr, lineBPtr); + } + } + else if (big == statusPtrQueue.size() - 1) { + // Check the last edge and the one before the previous edge + + LineSegment* lineAPtr = statusPtrQueue[small - 1]; + LineSegment* lineBPtr = statusPtrQueue[big]; + bool isCrossing = (*lineAPtr) * (*lineBPtr); + if (isCrossing) { + resultPtr->emplace_back(*lineAPtr); + resultPtr->emplace_back(*lineBPtr); + createNewEvent(lineAPtr, lineBPtr); + } + } + else { + // Check the first edge and the one after next(third) + LineSegment* lineAPtr = statusPtrQueue[small - 1]; + LineSegment* lineBPtr = statusPtrQueue[big]; + bool isCrossing = (*lineAPtr) * (*lineBPtr); + if (isCrossing) { + resultPtr->emplace_back(*lineAPtr); + resultPtr->emplace_back(*lineBPtr); + createNewEvent(lineAPtr, lineBPtr); + } + // Check the second edge and the one after next(forth) + LineSegment* lineCPtr = statusPtrQueue[small]; + LineSegment* lineDPtr = statusPtrQueue[big + 1]; + isCrossing = (*lineCPtr) * (*lineDPtr); + if (isCrossing) { + resultPtr->emplace_back(*lineCPtr); + resultPtr->emplace_back(*lineDPtr); + createNewEvent(lineCPtr, lineDPtr); + } + } + return false; +} diff --git a/uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.hpp b/uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.hpp new file mode 100644 index 0000000..c760e4d --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.hpp @@ -0,0 +1,34 @@ +// +// bentleyOttmann.hpp +// bentleyOttmann +// + +#pragma once + +#include "event.hpp" +#include "lineSegment.hpp" + +#include +#include +#include + +class BentleyOttmann { +public: + explicit BentleyOttmann(std::vector& edgeVector); + ~BentleyOttmann(); + + void check(std::vector &result); + std::vector edges; + + BentleyOttmann operator+(const BentleyOttmann& rhs) const; + +private: + std::vector *resultPtr{}; + bool doBegin(Event& ev); + bool doEnd(Event& ev); + bool doCross(Event& ev); + void createNewEvent(LineSegment* lineA, LineSegment* lineB); + + std::vector statusPtrQueue; + std::multiset eventQueue; +}; diff --git a/uvOverlapChecker/src/bentleyOttmann/event.cpp b/uvOverlapChecker/src/bentleyOttmann/event.cpp new file mode 100644 index 0000000..1441385 --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/event.cpp @@ -0,0 +1,32 @@ +// +// event.cpp +// bentleyOttmann +// + +#include "event.hpp" + +Event::Event() = default; + +Event::Event(int eventType, LineSegment* edgePtrA, const Point2D& point) + : eventType(eventType) + , edgePtrA(edgePtrA) + , eventPoint(point) + , sweepline(point.x) +{ +} + +Event::Event(int eventType, LineSegment* edgePtrA, LineSegment* edgePtrB, const Point2D& point) + : eventType(eventType) + , edgePtrA(edgePtrA) + , edgePtrB(edgePtrB) + , eventPoint(point) + , sweepline(point.x) +{ +} + +Event::~Event() = default; + +bool Event::operator<(const Event& rhs) const +{ + return this->eventPoint < rhs.eventPoint; +} diff --git a/uvOverlapChecker/src/bentleyOttmann/event.hpp b/uvOverlapChecker/src/bentleyOttmann/event.hpp new file mode 100644 index 0000000..c7e8252 --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/event.hpp @@ -0,0 +1,34 @@ +// +// event.hptp +// bentleyOttmann +// + +#pragma once + +#include "lineSegment.hpp" +#include "point2D.hpp" +#include + +class Event { +public: + Event(); + Event(int eventType, LineSegment* edgePtrA, const Point2D& point); + Event(int eventType, LineSegment* edgePtrA, LineSegment* edgePtrB, const Point2D& point); + ~Event(); + + int eventType{}; + LineSegment *edgePtrA{}, *edgePtrB{}; + Point2D eventPoint; + int index{}; + float sweepline{}; + + bool operator<(const Event& rhs) const; + + enum EVENT_TYPE { + BEGIN, + END, + CROSS + }; + +private: +}; diff --git a/uvOverlapChecker/src/bentleyOttmann/lineSegment.cpp b/uvOverlapChecker/src/bentleyOttmann/lineSegment.cpp new file mode 100644 index 0000000..8aa6b2e --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/lineSegment.cpp @@ -0,0 +1,124 @@ +// +// lineSegment.cpp +// bentleyOttmann +// + +#include "lineSegment.hpp" +#include +#include + +LineSegment::LineSegment() +{ +} + +LineSegment::LineSegment(Point2D p1, Point2D p2, const char* groupId) : groupId(groupId) +{ + if (p1 < p2) { + this->begin = p1; + this->end = p2; + this->index = std::make_pair(p1.index, p2.index); + } else { + this->begin = p2; + this->end = p1; + this->index = std::make_pair(p2.index, p1.index); + } + if (p1.x == p2.x) { + this->isVertical = true; + this->isHorizontal = false; + } else if (p1.y == p2.y) { + this->isVertical = false; + this->isHorizontal = true; + } + else { + this->isVertical = false; + this->isHorizontal = false; + } + +} + +LineSegment::~LineSegment() +{ +} + +bool LineSegment::operator==(const LineSegment& rhs) const +{ + return (this->begin == rhs.begin && this->end == rhs.end); +} + +float LineSegment::getTriangleArea(float Ax, float Ay, float Bx, float By, float Cx, float Cy) const +{ + return ((Ax * (By - Cy)) + (Bx * (Cy - Ay)) + (Cx * (Ay - By))) * 0.5F; +} + +bool LineSegment::sameSigns(const float x, const float y) const +{ + return (x >= 0) ^ (y < 0); +} + +bool LineSegment::operator*(const LineSegment& rhs) const +{ + // Check if two edges are on a same line + float t1 = getTriangleArea(this->begin.x, this->begin.y, rhs.begin.x, rhs.begin.y, this->end.x, this->end.y); + float t2 = getTriangleArea(this->begin.x, this->begin.y, rhs.end.x, rhs.end.y, this->end.x, this->end.y); + float t3 = getTriangleArea(rhs.begin.x, rhs.begin.y, this->begin.x, this->begin.y, rhs.end.x, rhs.end.y); + float t4 = getTriangleArea(rhs.begin.x, rhs.begin.y, this->end.x, this->end.y, rhs.end.x, rhs.end.y); + + if (t1 == 0 && t2 == 0 && t3 == 0 && t4 == 0) { + // Two lines are on a same line + Vector2D v1 = rhs.end - this->begin; + Vector2D v2 = rhs.begin - this->end; + v1.normalize(); + v2.normalize(); + float dot = v1 * v2; + if (dot >= 0) { + return false; + } else { + return true; + } + } + + if (t1 * t2 == 0 || t3 * t4 == 0) + return false; + + bool ccw1 = sameSigns(t1, t2); + bool ccw2 = sameSigns(t3, t4); + + if (ccw1 == false && ccw2 == false) { + return true; + } else { + return false; + } +} + +namespace lineUtils { +void getIntersectionPoint(const LineSegment& lineA, const LineSegment& lineB, float& x, float& y) +{ + + // Y = AX + B + float a1 = (lineA.end.y - lineA.begin.y) / (lineA.end.x - lineA.begin.x); + float a2 = (lineB.end.y - lineB.begin.y) / (lineB.end.x - lineB.begin.x); + float b1 = lineA.begin.y - (a1 * lineA.begin.x); + float b2 = lineB.begin.y - (a2 * lineB.begin.x); + + a1 = -1.0F * a1; + a2 = -1.0F * a2; + + // Matrix + // | (-a1) 1 || X | = | b1 | + // | (-a2) 1 || Y | = | b2 | + + float adbc = a1 - a2; + + // Get inverse matrix + + float a = 1.0F * (1.0F / adbc); + float b = -a2 * (1.0F / adbc); + float c = -1.0F * (1.0F / adbc); + float d = a1 * (1.0F / adbc); + + // [u] = [ a c ] [y_interceptA] + // [v] [ b d ] [y_interceptB] + x = (a * b1) + (c * b2); + y = (b * b1) + (d * b2); +} +} diff --git a/uvOverlapChecker/src/bentleyOttmann/lineSegment.hpp b/uvOverlapChecker/src/bentleyOttmann/lineSegment.hpp new file mode 100644 index 0000000..5e8bec9 --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/lineSegment.hpp @@ -0,0 +1,54 @@ +// +// lineSegment.hpp +// bentleyOttmann +// + +#pragma once + + +#include "point2D.hpp" +#include +#include + +class LineSegment { +public: + LineSegment(); + LineSegment(Point2D p1, Point2D p2, const char* groupId); + ~LineSegment(); + Point2D begin; + Point2D end; + float crossingPointY; + std::pair index; + const char* groupId; + + bool operator==(const LineSegment& rhs) const; + inline bool operator!=(const LineSegment& rhs) const + { + return !(*this == rhs); + } + + bool operator*(const LineSegment& rhs) const; + + bool isHorizontal; + bool isVertical; + +private: + float getTriangleArea(float Ax, float Ay, float Bx, float By, float Cx, float Cy) const; + bool sameSigns(float x, float y) const; +}; + +class EdgeCrossingComparator { +public: + bool operator()(const LineSegment* left, const LineSegment* right) const + { + if (left->crossingPointY == right->crossingPointY) { + return left->end.y < right->end.y; + } else { + return left->crossingPointY < right->crossingPointY; + } + } +}; + +namespace lineUtils { +void getIntersectionPoint(const LineSegment& lineA, const LineSegment& lineB, float& x, float& y); +} diff --git a/uvOverlapChecker/src/bentleyOttmann/main.cpp b/uvOverlapChecker/src/bentleyOttmann/main.cpp new file mode 100644 index 0000000..d9821c5 --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/main.cpp @@ -0,0 +1,88 @@ +// +// main.cpp +// bentleyOttman +// + +#include +#include +#include + +#include "bentleyOttmann.hpp" +#include "lineSegment.hpp" +#include "point2D.hpp" +#include "testData/dataSet.hpp" + + +int test() { + + std::vector edgeVec; + std::vector edgeVec2; + + std::string dataPathA = "./testData/DataA-Table_1.csv"; + std::string dataPathB = "./testData/DataB-Table_1.csv"; + std::vector > v1 = TestDataSet::getDataSet(dataPathA); + std::vector > v2 = TestDataSet::getDataSet(dataPathB); + + for (size_t i = 0; i < v1.size(); i++) { + int indexA = std::stoi(v1[i][0]); + float x1 = std::stof(v1[i][1]); + float y1 = std::stof(v1[i][2]); + + int indexB = std::stoi(v1[i][3]); + float x2 = std::stof(v1[i][4]); + float y2 = std::stof(v1[i][5]); + + Point2D p1(x1, y1, indexA); + Point2D p2(x2, y2, indexB); + + LineSegment edge(p1, p2); + edgeVec.push_back(edge); + } + + for (size_t i = 0; i < v2.size(); i++) { + int indexA = std::stoi(v2[i][0]); + float x1 = std::stof(v2[i][1]); + float y1 = std::stof(v2[i][2]); + + int indexB = std::stoi(v2[i][3]); + float x2 = std::stof(v2[i][4]); + float y2 = std::stof(v2[i][5]); + + Point2D p1(x1, y1, indexA); + Point2D p2(x2, y2, indexB); + + LineSegment edge(p1, p2); + edgeVec2.push_back(edge); + } + + // Remove duplicated line segments + std::sort(edgeVec.begin(), edgeVec.end(), EdgeIndexComparator()); + edgeVec.erase(std::unique(edgeVec.begin(), edgeVec.end()), edgeVec.end()); + + std::sort(edgeVec2.begin(), edgeVec2.end(), EdgeIndexComparator()); + edgeVec.erase(std::unique(edgeVec2.begin(), edgeVec2.end()), edgeVec2.end()); + + // Name line group name if necessary + std::string groupNameA = "lineGroupA"; + std::string groupNameB = "lineGroupB"; + BentleyOttman checker(edgeVec, groupNameA); + BentleyOttman checker2(edgeVec2, groupNameB); + + BentleyOttman checker3 = checker + checker2; + checker3.check(); + + std::vector::iterator resultIter; + for (resultIter = checker3.resultPtr.begin(); resultIter != checker3.resultPtr.end(); ++resultIter) { + const LineSegment& line = *(*resultIter); + std::cout << line.begin.index << ":" << line.end.index << " :: " << std::endl; + std::cout << line.groupId << std::endl; + } + return 0; +} + +int main(int argc, const char* argv[]) +{ + test(); + std::cout << "end" << std::endl; + return 0; +} diff --git a/uvOverlapChecker/src/bentleyOttmann/point2D.cpp b/uvOverlapChecker/src/bentleyOttmann/point2D.cpp new file mode 100644 index 0000000..2eff73c --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/point2D.cpp @@ -0,0 +1,43 @@ +// +// point2D.cpp +// bentleyOttmann +// + +#include "point2D.hpp" + +Point2D::Point2D() += default; + +Point2D::Point2D(float x, float y, int index) + : x(x) + , y(y) + , index(index) + , xy(std::make_pair(x, y)) +{ +} + +Point2D::~Point2D() += default; + +bool Point2D::operator==(const Point2D& rhs) const +{ + return this->index == rhs.index; +} + +bool Point2D::operator<(const Point2D& rhs) const +{ + return this->xy < rhs.xy; +} + +Vector2D Point2D::operator-(const Point2D& rhs) const +{ + if (this->index == rhs.index) { + Vector2D v(0, 0); + return v; + } + + float value_x = rhs.x - this->x; + float value_y = rhs.y - this->y; + Vector2D v(value_x, value_y); + return v; +} diff --git a/uvOverlapChecker/src/bentleyOttmann/point2D.hpp b/uvOverlapChecker/src/bentleyOttmann/point2D.hpp new file mode 100644 index 0000000..add0877 --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/point2D.hpp @@ -0,0 +1,28 @@ +// +// point2D.hpp +// bentleyOttmann +// + +#pragma once + +#include "vector2D.hpp" +#include + +class Point2D { +public: + Point2D(); + Point2D(float x, float y, int index); + ~Point2D(); + float x{}, y{}; + int index{}; + std::pair xy; + + bool operator==(const Point2D& rhs) const; + inline bool operator!=(const Point2D& rhs) const + { + return !(*this == rhs); + } + + bool operator<(const Point2D& rhs) const; + Vector2D operator-(const Point2D& rhs) const; +}; diff --git a/uvOverlapChecker/src/bentleyOttmann/testData/DataA-Table_1.csv b/uvOverlapChecker/src/bentleyOttmann/testData/DataA-Table_1.csv new file mode 100644 index 0000000..62d7691 --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/testData/DataA-Table_1.csv @@ -0,0 +1,22 @@ +0,0.0660550594329834,0.0660550594329834,1,0.2830275297164917,0.0660550594329834 +1,0.2830275297164917,0.0660550594329834,6,0.2830275297164917,0.2830275297164917 +6,0.2830275297164917,0.2830275297164917,5,0.0660550594329834,0.2830275297164917 +5,0.0660550594329834,0.2830275297164917,0,0.0660550594329834,0.0660550594329834 +1,0.2830275297164917,0.0660550594329834,2,0.50,0.0660550594329834 +2,0.50,0.0660550594329834,7,0.770054817199707,0.36120128631591797 +7,0.770054817199707,0.36120128631591797,6,0.2830275297164917,0.2830275297164917 +2,0.50,0.0660550594329834,3,0.7169724702835083,0.0660550594329834 +3,0.7169724702835083,0.0660550594329834,8,0.7596127390861511,0.23564951121807098 +8,0.7596127390861511,0.23564951121807098,7,0.770054817199707,0.36120128631591797 +3,0.7169724702835083,0.0660550594329834,4,0.9292071461677551,0.11106419563293457 +4,0.9292071461677551,0.11106419563293457,9,0.9339449405670166,0.2830275297164917 +9,0.9339449405670166,0.2830275297164917,8,0.7596127390861511,0.23564951121807098 +6,0.2830275297164917,0.2830275297164917,28,0.2830275297164917,0.5 +28,0.2830275297164917,0.5,29,0.0660550594329834,0.5 +29,0.0660550594329834,0.5,5,0.0660550594329834,0.2830275297164917 +7,0.770054817199707,0.36120128631591797,27,0.5,0.5 +27,0.5,0.5,28,0.2830275297164917,0.5 +8,0.7596127390861511,0.23564951121807098,25,0.7169724702835083,0.5 +25,0.7169724702835083,0.5,27,0.5,0.5 +9,0.9339449405670166,0.2830275297164917,14,0.9339449405670166,0.5 +14,0.9339449405670166,0.5,25,0.7169724702835083,0.5 \ No newline at end of file diff --git a/uvOverlapChecker/src/bentleyOttmann/testData/DataB-Table_1.csv b/uvOverlapChecker/src/bentleyOttmann/testData/DataB-Table_1.csv new file mode 100644 index 0000000..12c1b92 --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/testData/DataB-Table_1.csv @@ -0,0 +1,21 @@ +10,-0.5314721465110779,0.6870385408401489,11,-0.5849046111106873,0.8497003316879272 +11,-0.5849046111106873,0.8497003316879272,16,-0.29304349422454834,0.8890276551246643 +15,-0.47460252046585083,0.8964254856109619,10,-0.5314721465110779,0.6870385408401489 +11,-0.5849046111106873,0.8497003316879272,12,-0.1126982569694519,0.5732992887496948 +12,-0.1126982569694519,0.5732992887496948,17,-0.055828630924224854,0.782686173915863 +17,-0.055828630924224854,0.782686173915863,16,-0.29304349422454834,0.8890276551246643 +12,-0.1126982569694519,0.5732992887496948,13,0.09668868780136108,0.5164296627044678 +13,0.09668868780136108,0.5164296627044678,18,0.19084149599075317,0.6371393203735352 +18,0.19084149599075317,0.6371393203735352,17,-0.055828630924224854,0.782686173915863 +13,0.09668868780136108,0.5164296627044678,26,0.3060756325721741,0.45955994725227356 +26,0.3060756325721741,0.45955994725227356,19,0.3629452586174011,0.6689468026161194 +19,0.3629452586174011,0.6689468026161194,18,0.19084149599075317,0.6371393203735352 +16,-0.29304349422454834,0.8890276551246643,21,-0.2083459496498108,1.0489426851272583 +21,-0.2083459496498108,1.0489426851272583,20,-0.4177328944206238,1.105812430381775 +20,-0.4177328944206238,1.105812430381775,15,-0.47460252046585083,0.8964254856109619 +17,-0.055828630924224854,0.782686173915863,22,0.004851400852203369,0.9247608184814453 +22,0.004851400852203369,0.9247608184814453,21,-0.2083459496498108,1.0489426851272583 +18,0.19084149599075317,0.6371393203735352,23,0.21042793989181519,0.935203492641449 +23,0.21042793989181519,0.935203492641449,22,0.004851400852203369,0.9247608184814453 +19,0.3629452586174011,0.6689468026161194,24,0.4198148846626282,0.8783338665962219 +24,0.4198148846626282,0.8783338665962219,23,0.21042793989181519,0.935203492641449 \ No newline at end of file diff --git a/uvOverlapChecker/src/bentleyOttmann/testData/dataSet.cpp b/uvOverlapChecker/src/bentleyOttmann/testData/dataSet.cpp new file mode 100644 index 0000000..b259017 --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/testData/dataSet.cpp @@ -0,0 +1,30 @@ +// +// dataSet.cpp +// bentleyOttman +// + +#include "dataSet.hpp" +#include +#include +#include + +std::vector > TestDataSet::getDataSet(std::string path) +{ + std::ifstream ifs(path.c_str()); + if (!ifs) { + std::cout << "Failed to open file" << std::endl; + } + std::string str; + std::vector > v; + + while (std::getline(ifs, str)) { + std::istringstream stream(str); + std::string field; + std::vector result; + while (std::getline(stream, field, ',')) { + result.push_back(field); + } + v.push_back(result); + } + return v; +} diff --git a/uvOverlapChecker/src/bentleyOttmann/testData/dataSet.hpp b/uvOverlapChecker/src/bentleyOttmann/testData/dataSet.hpp new file mode 100644 index 0000000..a4a0a2a --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/testData/dataSet.hpp @@ -0,0 +1,17 @@ +// +// dataSet.hpp +// bentleyOttman +// + +#ifndef dataSet_hpp +#define dataSet_hpp + +#include +#include + +class TestDataSet { +public: + static std::vector > getDataSet(std::string path); +}; + +#endif /* dataSet_hpp */ diff --git a/uvOverlapChecker/src/bentleyOttmann/vector2D.cpp b/uvOverlapChecker/src/bentleyOttmann/vector2D.cpp new file mode 100644 index 0000000..d0a8751 --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/vector2D.cpp @@ -0,0 +1,41 @@ +// +// vector2D.cpp +// bentleyOttmann +// + +#include "vector2D.hpp" +#include + +Vector2D::Vector2D() += default; + +Vector2D::Vector2D(float x, float y) + : x(x) + , y(y) +{ +} + +Vector2D::~Vector2D() += default; + +void Vector2D::normalize() +{ + float m = getLength(); + if (m != 0) { + this->x = this->x / m; + this->y = this->y / m; + } +} + +float Vector2D::getLength() +{ + float& value_x = this->x; + float& value_y = this->y; + return std::sqrt(value_x * value_x + value_y * value_y); +} + +float Vector2D::operator*(const Vector2D& rhs) const +{ + float dot = (this->x * rhs.x) + (this->y * rhs.y); + return dot; +} diff --git a/uvOverlapChecker/src/bentleyOttmann/vector2D.hpp b/uvOverlapChecker/src/bentleyOttmann/vector2D.hpp new file mode 100644 index 0000000..0cb102e --- /dev/null +++ b/uvOverlapChecker/src/bentleyOttmann/vector2D.hpp @@ -0,0 +1,19 @@ +// +// vector2D.hpp +// bentleyOttmann +// + +#pragma once + +class Vector2D { +public: + Vector2D(); + Vector2D(float x, float y); + ~Vector2D(); + float x{}, y{}; + + void normalize(); + float getLength(); + + float operator*(const Vector2D& rhs) const; +}; diff --git a/uvOverlapChecker/src/findUvOverlaps.cpp b/uvOverlapChecker/src/findUvOverlaps.cpp new file mode 100644 index 0000000..c2ec545 --- /dev/null +++ b/uvOverlapChecker/src/findUvOverlaps.cpp @@ -0,0 +1,392 @@ +#include +#include +#include +#include +#include +#include +#include "findUvOverlaps.hpp" +#include +#include +#include +#include +#include +#include +#include + +static const char* const pluginCommandName = "findUvOverlaps"; +static const char* const pluginVersion = "1.8.20"; +static const char* const pluginAuthor = "Michitaka Inoue"; + +void UVShell::initAABB() +{ + std::vector uVector; + std::vector vVector; + for (auto & line : this->lines) { + uVector.emplace_back(line.begin.x); + uVector.emplace_back(line.end.x); + vVector.emplace_back(line.begin.y); + vVector.emplace_back(line.end.y); + } + this->left = *std::min_element(uVector.begin(), uVector.end()); + this->right = *std::max_element(uVector.begin(), uVector.end()); + this->bottom = *std::min_element(vVector.begin(), vVector.end()); + this->top = *std::max_element(vVector.begin(), vVector.end()); +} + +bool UVShell::operator*(const UVShell& other) const +{ + if (this->right < other.left) + return false; + + if (this->left > other.right) + return false; + + if (this->top < other.bottom) + return false; + + if (this->bottom > other.top) + return false; + + return true; +} + +UVShell UVShell::operator&&(const UVShell& other) const +{ + std::vector lineVector = this->lines; + lineVector.insert(lineVector.end(), other.lines.begin(), other.lines.end()); + UVShell shell; + shell.lines = lineVector; + return shell; +} + +FindUvOverlaps::FindUvOverlaps() + : verbose(false) {} + +FindUvOverlaps::~FindUvOverlaps() = default; + +void* FindUvOverlaps::creator() +{ + return new FindUvOverlaps(); +} + +MSyntax FindUvOverlaps::newSyntax() +{ + MSyntax syntax; + syntax.addFlag("-v", "-verbose", MSyntax::kBoolean); + syntax.addFlag("-set", "-uvSet", MSyntax::kString); + return syntax; +} + +void FindUvOverlaps::btoCheck(UVShell& shell) +{ + std::vector result; + BentleyOttmann b(shell.lines); + b.check(result); + pushToLineVector(result); +} + +void FindUvOverlaps::pushToLineVector(std::vector& v) +{ + try { + locker.lock(); + finalResult.push_back(v); + locker.unlock(); + } catch (std::exception& e) { + std::cerr << e.what() << std::endl; + } +} + +void FindUvOverlaps::pushToShellVector(UVShell& shell) +{ + try { + locker.lock(); + shellVector.push_back(shell); + locker.unlock(); + } catch (std::exception& e) { + std::cerr << e.what() << std::endl; + } +} + +MStatus FindUvOverlaps::doIt(const MArgList& args) +{ + MStatus stat; + MTimer timer; + double elapsedTime; + + MArgDatabase argData(syntax(), args); + + if (argData.isFlagSet("-verbose")) + argData.getFlagArgument("-verbose", 0, verbose); + else + verbose = false; + + if (argData.isFlagSet("-uvSet")) + argData.getFlagArgument("-uvSet", 0, uvSet); + else + uvSet = "None"; + + MGlobal::getActiveSelectionList(mSel); + + timer.beginTimer(); + // Multithrad obj initialization + // std::thread* threadArray = new std::thread[mSel.length()]; + // for (size_t i = 0; i < mSel.length(); i++) { + // threadArray[i] = std::thread(&FindUvOverlaps::init, this, i); + // } + // for (size_t i = 0; i < mSel.length(); i++) { + // threadArray[i].join(); + // } + // delete[] threadArray; + + int numSelected = static_cast(mSel.length()); +#pragma omp parallel for + for (int i = 0; i < numSelected; i++) { + init(i); + } + + timer.endTimer(); + elapsedTime = timer.elapsedTime(); + if (verbose) + timeIt("Init time : ", elapsedTime); + timer.clear(); + + size_t numAllShells = shellVector.size(); + + for (size_t i = 0; i < numAllShells; i++) { + UVShell& s = shellVector[i]; + s.initAABB(); + } + + std::vector shells; // temp countainer to store both original and combined shells + + for (size_t i = 0; i < numAllShells; i++) { + UVShell& shellA = shellVector[i]; + shells.push_back(shellA); + + for (size_t j = i + 1; j < numAllShells; j++) { + UVShell& shellB = shellVector[j]; + + if (shellA * shellB) { + UVShell intersectedShell = shellA && shellB; + shells.push_back(intersectedShell); + } + } + } + + timer.beginTimer(); + + if (verbose) { + MString numShellsStr; + numShellsStr.set(static_cast(shells.size())); + MGlobal::displayInfo("Number of UvShells : " + numShellsStr); + } + + // Multithread bentleyOttman check + size_t numAllShells2 = shells.size(); + auto* btoThreadArray = new std::thread[numAllShells2]; + for (size_t i = 0; i < numAllShells2; i++) { + UVShell& s = shells[i]; + btoThreadArray[i] = std::thread(&FindUvOverlaps::btoCheck, this, std::ref(s)); + } + for (size_t i = 0; i < numAllShells2; i++) { + btoThreadArray[i].join(); + } + delete[] btoThreadArray; + + timer.endTimer(); + elapsedTime = timer.elapsedTime(); + if (verbose) + timeIt("Check time : ", elapsedTime); + timer.clear(); + + timer.beginTimer(); + // Re-insert to set to remove duplicates + std::string temp_path; + std::unordered_set temp; + for (auto&& lines : finalResult) { + for (auto&& line : lines) { + std::string groupName(line.groupId); + temp_path = groupName + ".map[" + std::to_string(line.index.first) + "]"; + temp.insert(temp_path); + temp_path = groupName + ".map[" + std::to_string(line.index.second) + "]"; + temp.insert(temp_path); + } + } + + // Remove duplicates + timer.endTimer(); + elapsedTime = timer.elapsedTime(); + if (verbose) + timeIt("Removed duplicates : ", elapsedTime); + timer.clear(); + + // Insert all results to MStringArray for return + MString s; + MStringArray resultStringArray; + + for (const auto& fullpath : temp) { + s.set(fullpath.c_str()); + resultStringArray.append(s); + } + + setResult(resultStringArray); + + return MS::kSuccess; +} + +MStatus FindUvOverlaps::init(int i) +{ + MStatus status; + + MDagPath dagPath; + mSel.getDagPath(static_cast(i), dagPath); + + // Check if specified object is geometry or not + status = dagPath.extendToShape(); + if (status != MS::kSuccess) { + if (verbose) + MGlobal::displayInfo("Failed to extend to shape node."); + return MS::kFailure; + } + + if (dagPath.apiType() != MFn::kMesh) { + if (verbose) + MGlobal::displayInfo("Selected node : " + dagPath.fullPathName() + " is not mesh. Skipped"); + return MS::kFailure; + } + + MFnMesh fnMesh(dagPath); + + // Send to path vector and get pointer to that + const char* dagPathChar = paths.emplace_back(dagPath.fullPathName()); + + MIntArray uvShellIds; + unsigned int nbUvShells; + fnMesh.getUvShellsIds(uvShellIds, nbUvShells); + + MIntArray uvCounts; // Num of UVs per face eg. [4, 4, 4, 4, ...] + MIntArray uvIds; + fnMesh.getAssignedUVs(uvCounts, uvIds); + + unsigned int uvCountSize = uvCounts.length(); // is same as number of faces + std::vector> idPairs; + idPairs.reserve(uvCountSize * 4); + unsigned int uvCounter = 0; + unsigned int nextCounter; + // Loop over each face and its edges, then create a pair of indices + for (unsigned int j = 0; j < uvCountSize; j++) { + auto numFaceUVs = static_cast(uvCounts[j]); + for (unsigned int localIndex = 0; localIndex < numFaceUVs; localIndex++) { + if (localIndex == numFaceUVs - 1) { + // Set the nextCounter to the localIndex of zero of the face + nextCounter = uvCounter - numFaceUVs + 1; + } else { + nextCounter = uvCounter + 1; + } + + int idA = uvIds[uvCounter]; + int idB = uvIds[nextCounter]; + + std::pair idPair; + + if (idA < idB) + idPair = std::make_pair(idA, idB); + else + idPair = std::make_pair(idB, idA); + + idPairs.emplace_back(idPair); + uvCounter++; + } + } + + // Remove duplicate elements + std::sort(idPairs.begin(), idPairs.end()); + idPairs.erase(std::unique(idPairs.begin(), idPairs.end()), idPairs.end()); + + // Temp countainer for lineSegments for each UVShell + std::vector> edgeVector; + edgeVector.resize(nbUvShells); + + MFloatArray uArray; + MFloatArray vArray; + fnMesh.getUVs(uArray, vArray); + + // Setup uv shell objects + std::vector shells(nbUvShells); + + // Loop over all id pairs and create lineSegment objects + for (auto & idPair : idPairs) { + + unsigned int idA = idPair.first; + unsigned int idB = idPair.second; + Point2D p1(uArray[idA], vArray[idA], static_cast(idA)); + Point2D p2(uArray[idB], vArray[idB], static_cast(idB)); + + // Create new lineSegment object + LineSegment line(p1, p2, dagPathChar); + + auto shellIndex = static_cast(uvShellIds[idA]); + shells[shellIndex].lines.emplace_back(line); + } + + for (auto & shell : shells) { + pushToShellVector(shell); + } + + return MS::kSuccess; +} + +void FindUvOverlaps::timeIt(const std::string& text, double t) +{ + MString message, time; + message.set(text.c_str()); + time.set(t); + MGlobal::displayInfo(message + " : " + time + " seconds."); +} + +// +// The following routines are used to register/unregister +// the command we are creating within Maya +// + +MStatus initializePlugin(MObject obj) +{ + MStatus status; + + std::string version_str(pluginVersion); + std::string compile_date_str(__DATE__); + std::string compile_time_str(__TIME__); + std::string version(version_str + " / " + compile_date_str + " / " + compile_time_str); + + MFnPlugin plugin(obj, pluginAuthor, version.c_str(), "Any"); + + status = plugin.registerCommand(pluginCommandName, FindUvOverlaps::creator, FindUvOverlaps::newSyntax); + if (!status) { + status.perror("registerCommand"); + return status; + } + + return status; +} + +MStatus uninitializePlugin(MObject obj) +{ + MStatus status; + MFnPlugin plugin(obj); + + status = plugin.deregisterCommand(pluginCommandName); + if (!status) { + status.perror("deregisterCommand"); + return status; + } + + return status; +} + +const char *MStringVector::emplace_back(const MString& path) { + std::lock_guard lock(mtx); + elements.emplace_back(path); + MString &tempMstring = elements.back(); + const char* tempChar = tempMstring.asChar(); + return tempChar; +} diff --git a/uvOverlapChecker/src/findUvOverlaps.hpp b/uvOverlapChecker/src/findUvOverlaps.hpp new file mode 100644 index 0000000..cd846c6 --- /dev/null +++ b/uvOverlapChecker/src/findUvOverlaps.hpp @@ -0,0 +1,57 @@ +#pragma once + + +#include "bentleyOttmann/bentleyOttmann.hpp" +#include "bentleyOttmann/lineSegment.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +class UVShell { + float left, right, top, bottom; +public: + std::vector lines; + void initAABB(); + bool operator*(const UVShell& other) const; + UVShell operator&&(const UVShell& other) const; +}; + +class MStringVector { +private: + std::mutex mtx; + std::vector elements; +public: + const char* emplace_back(const MString& path); +}; + +class FindUvOverlaps : public MPxCommand { +public: + FindUvOverlaps(); + ~FindUvOverlaps() override; + + MStatus doIt(const MArgList& args) override; + + static void* creator(); + static MSyntax newSyntax(); + +private: + std::mutex locker; + MString uvSet; + bool verbose; + MSelectionList mSel; + MStringVector paths; + + std::vector > finalResult; + std::vector shellVector; + + MStatus init(int i); + void btoCheck(UVShell &shell); + void pushToLineVector(std::vector &v); + void pushToShellVector(UVShell &shell); + static void timeIt(const std::string& text, double t); +};