Initial commit

This commit is contained in:
Jcen 2024-10-01 19:48:12 +08:00
parent c8aa77b549
commit d13d83b1ff
40 changed files with 3294 additions and 33 deletions

65
.gitignore vendored
View File

@ -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

113
CMakeLists.txt Normal file
View File

@ -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
$<$<CXX_COMPILER_ID:GNU>:pthread -Wall -Wextra -Wconversion -Wsign-conversion -pedantic>
$<$<CXX_COMPILER_ID:MSVC>:/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}
$<$<CXX_COMPILER_ID:MSVC>: 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})

21
LICENSE.md Normal file
View File

@ -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.

View File

@ -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 BentleyOttmann 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
```

BIN
images/UVsInNegative.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/concaveUVs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/reversedUVs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
images/unMappedFaces.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
images/zeroAreaUVFaces.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

98
include/ThreadPool.hpp Normal file
View File

@ -0,0 +1,98 @@
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
class ThreadPool {
public:
ThreadPool(size_t);
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::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<void()> > 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<threads;++i)
workers.emplace_back(
[this]
{
for(;;)
{
std::function<void()> task;
{
std::unique_lock<std::mutex> 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<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>
{
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared< std::packaged_task<return_type()> >(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> 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<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(std::thread &worker: workers)
worker.join();
}
#endif

61
include/utils.hpp Normal file
View File

@ -0,0 +1,61 @@
#pragma once
#include <string>
#include <vector>
#include <maya/MDagPath.h>
#include <maya/MString.h>
#include <maya/MItDag.h>
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<std::string>& 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());
}
}
}

View File

@ -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})

35
meshChecker/README.md Normal file
View File

@ -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]']
```

View File

@ -0,0 +1,723 @@
#include "meshChecker.hpp"
#include "../../include/ThreadPool.hpp"
#include "../../include/utils.hpp"
#include "maya/MApiNamespace.h"
#include <cstddef>
#include <maya/MObject.h>
#include <maya/MArgDatabase.h>
#include <maya/MArgList.h>
#include <maya/MArrayDataHandle.h>
#include <maya/MDagPath.h>
#include <maya/MDataHandle.h>
#include <maya/MDoubleArray.h>
#include <maya/MFnDagNode.h>
#include <maya/MFnMesh.h>
#include <maya/MFnPlugin.h>
#include <maya/MGlobal.h>
#include <maya/MItDag.h>
#include <maya/MItMeshEdge.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MItMeshVertex.h>
#include <maya/MPlug.h>
#include <maya/MSelectionList.h>
#include <maya/MString.h>
#include <maya/MSyntax.h>
#include <maya/MUintArray.h>
#include <maya/MPlugArray.h>
#include <cmath>
#include <string>
#include <thread>
#include <algorithm>
static const char* const pluginCommandName = "checkMesh";
static const char* const pluginVersion = "2.3.0";
static const char* const pluginAuthor = "Michi Inoue";
namespace {
std::vector<std::string> findTriangles(std::vector<std::string>* paths)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> 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<std::string> findNgons(std::vector<std::string>* paths)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> 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<std::string> findNonManifoldEdges(std::vector<std::string>* paths)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> 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<std::string> findLaminaFaces(std::vector<std::string>* paths)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> 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<int>(polyIter.index()), errorPath);
result.push_back(errorPath);
}
}
}
return result;
}
std::vector<std::string> findBiValentFaces(std::vector<std::string>* paths)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> 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<std::string> findZeroAreaFaces(std::vector<std::string>* paths, double maxFaceArea)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> 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<int>(polyIter.index()), errorPath);
result.push_back(errorPath);
}
}
}
return result;
}
std::vector<std::string> findMeshBorderEdges(std::vector<std::string>* paths)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> 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<std::string> findCreaseEdges(std::vector<std::string>* paths)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
unsigned int length = list.length();
std::vector<std::string> 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<int>(edgeIds[j]), errorPath);
result.push_back(errorPath);
}
}
return result;
}
std::vector<std::string> findZeroLengthEdges(std::vector<std::string>* paths, double minEdgeLength)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> 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<int>(edgeIter.index()), errorPath);
result.push_back(errorPath);
}
}
}
return result;
}
std::vector<std::string> hasVertexPntsAttr(std::vector<std::string>* paths)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> 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<std::string> isEmptyGeometry(std::vector<std::string>* paths)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> 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<std::string> findUnusedVertices(std::vector<std::string>* paths)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> 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<std::string> findInstances(std::vector<std::string>* paths)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> 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<std::string> findConnections(std::vector<std::string>* paths)
{
MSelectionList list;
for (auto& p : *paths) {
MString mPath(p.c_str());
list.add(mPath);
}
std::vector<std::string> CON_LIST = {
"translateX",
"translateY",
"translateZ",
"rotateX",
"rotateY",
"rotateZ",
"rotateOrder",
"parentInverseMatrix",
"rotatePivot",
"rotatePivotTranslate"
};
std::vector<std::string> 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<numPlugs; j++) {
MPlug p = plugs[j];
MString cn = p.partialName(false, false, false, false, false, true);
if (std::find(CON_LIST.begin(), CON_LIST.end(), cn.asChar()) != CON_LIST.end()) {
result.push_back(dagPath.fullPathName().asChar());
break;
}
}
}
return result;
}
} // namespace
MeshChecker::MeshChecker()
: MPxCommand()
{
}
MStatus MeshChecker::doIt(const MArgList& args)
{
MStatus status;
MArgDatabase argData(syntax(), args);
// if argument is not provided use selection list
MSelectionList selection;
if (args.length() == 0) {
MGlobal::getActiveSelectionList(selection);
} else {
status = argData.getCommandArgument(0, selection);
CHECK_MSTATUS_AND_RETURN_IT(status)
if (status != MS::kSuccess) {
return MStatus::kFailure;
}
}
// mesh construction
MDagPath path;
selection.getDagPath(0, path);
// argument parsing
MeshCheckType check_type;
if (argData.isFlagSet("-check")) {
unsigned int check_value;
argData.getFlagArgument("-check", 0, check_value);
check_type = static_cast<MeshCheckType>(check_value);
// TODO check if exeds value
} else {
MGlobal::displayError("Check type required.");
return MS::kFailure;
}
std::vector<std::string> hierarchy;
buildHierarchy(path, hierarchy);
// Number of threads to use
size_t numTasks = 8;
// Split sub-vectors to pass to each thread
std::vector<std::vector<std::string>> 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<std::future<std::vector<std::string>>> 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<std::string> intermediateResult;
for (auto&& result : results) {
std::vector<std::string> 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;
}

View File

@ -0,0 +1,36 @@
#pragma once
#include <maya/MPxCommand.h>
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();
};

15
uvChecker/CMakeLists.txt Normal file
View File

@ -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})

50
uvChecker/README.md Normal file
View File

@ -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]', ...]
```

View File

@ -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()

545
uvChecker/src/uvChecker.cpp Normal file
View File

@ -0,0 +1,545 @@
#include "uvChecker.hpp"
#include "../../include/ThreadPool.hpp"
#include "../../include/utils.hpp"
#include <maya/MArgDatabase.h>
#include <maya/MArgList.h>
#include <maya/MDagPath.h>
#include <maya/MFloatArray.h>
#include <maya/MFnMesh.h>
#include <maya/MFnPlugin.h>
#include <maya/MGlobal.h>
#include <maya/MItDag.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MPointArray.h>
#include <maya/MSelectionList.h>
#include <algorithm>
#include <cstddef>
#include <string>
#include <thread>
#include <unordered_set>
static const char* const pluginCommandName = "checkUV";
static const char* const pluginVersion = "2.1.3";
static const char* const pluginAuthor = "Michi Inoue";
namespace {
std::vector<std::string> findUdimIntersections(std::vector<std::string>* 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<std::string> result2;
std::string errorPath;
for (unsigned int i = 0; i < length; i++) {
list.getDagPath(i, dagPath);
mesh.setObject(dagPath);
std::vector<int> indices;
for (MItMeshPolygon mItPoly(dagPath); !mItPoly.isDone(); mItPoly.next()) {
int vCount = static_cast<int>(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<std::string> findNoUvFaces(std::vector<std::string>* 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<std::string> 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<int>(itPoly.index()), errorPath);
result2.push_back(errorPath);
}
}
}
return result2;
}
std::vector<std::string> findZeroUvFaces(std::vector<std::string>* 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<std::string> 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<int>(itPoly.index()), errorPath);
result2.push_back(errorPath);
}
}
}
}
return result2;
}
std::vector<std::string> hasUnassignedUVs(std::vector<std::string>* 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<std::string> 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<int> uvIdSet;
for (unsigned int i = 0; i < numUvIds; i++) {
uvIdSet.insert(uvIds[i]);
}
int numAssignedUVs = static_cast<int>(uvIdSet.size());
if (numUVs != numAssignedUVs) {
result2.push_back(dagPath.fullPathName().asChar());
}
}
return result2;
}
std::vector<std::string> findNegativeSpaceUVs(std::vector<std::string>* 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<std::string> 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<unsigned int>(j)];
if (u < 0.0) {
createResultString(dagPath, ResultType::UV, j, errorPath);
result2.push_back(errorPath);
continue;
}
float& v = vArray[static_cast<unsigned int>(j)];
if (v < 0.0) {
createResultString(dagPath, ResultType::UV, j, errorPath);
result2.push_back(errorPath);
continue;
}
}
}
return result2;
}
std::vector<std::string> findConcaveUVs(std::vector<std::string>* 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<std::string> result2;
std::string errorPath;
for (unsigned int i = 0; i < length; i++) {
list.getDagPath(i, dagPath);
std::vector<int> 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<int>(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<int>(polygonIndex));
}
} else {
if (S >= -0.00000000001) {
indices.push_back(static_cast<int>(polygonIndex));
}
}
}
}
for (auto& index : indices) {
createResultString(dagPath, ResultType::Face, index, errorPath);
result2.push_back(errorPath);
}
}
return result2;
}
std::vector<std::string> findReversedUVs(std::vector<std::string>* 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<std::string> 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<int>(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<UVCheckType>(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<std::string> hierarchy;
buildHierarchy(path, hierarchy);
// Number of threads to use
size_t numTasks = 8;
// Split sub-vectors to pass to each thread
std::vector<std::vector<std::string>> 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<std::future<std::vector<std::string>>> 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<std::string> intermediateResult;
for (auto&& result : results) {
std::vector<std::string> 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;
}

View File

@ -0,0 +1,36 @@
#pragma once
#include <maya/MPxCommand.h>
#include <maya/MString.h>
#include <maya/MSyntax.h>
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;
};

View File

@ -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})

View File

@ -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
<img src="https://github.com/minoue/CheckTools/blob/media/media/uvOverlaps_single.gif" alt="Image" style="width: 300px;"/>
* Multiple object selected
<img src="https://github.com/minoue/CheckTools/blob/media/media/uvOverlaps_multipleObj.gif" alt="Image" style="width: 300px;"/>

View File

@ -0,0 +1,263 @@
//
// bentleyOttmann.cpp
// bentleyOttmann
//
#include "bentleyOttmann.hpp"
#include <algorithm>
#include <iostream>
BentleyOttmann::BentleyOttmann(std::vector<LineSegment>& 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<LineSegment> 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<LineSegment> &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<size_t>(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<size_t>(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<size_t>(std::distance(statusPtrQueue.begin(), lineAPtrIter));
size_t lineBIndex = static_cast<size_t>(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<size_t>(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;
}

View File

@ -0,0 +1,34 @@
//
// bentleyOttmann.hpp
// bentleyOttmann
//
#pragma once
#include "event.hpp"
#include "lineSegment.hpp"
#include <set>
#include <string>
#include <vector>
class BentleyOttmann {
public:
explicit BentleyOttmann(std::vector<LineSegment>& edgeVector);
~BentleyOttmann();
void check(std::vector<LineSegment> &result);
std::vector<LineSegment> edges;
BentleyOttmann operator+(const BentleyOttmann& rhs) const;
private:
std::vector<LineSegment> *resultPtr{};
bool doBegin(Event& ev);
bool doEnd(Event& ev);
bool doCross(Event& ev);
void createNewEvent(LineSegment* lineA, LineSegment* lineB);
std::vector<LineSegment*> statusPtrQueue;
std::multiset<Event> eventQueue;
};

View File

@ -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;
}

View File

@ -0,0 +1,34 @@
//
// event.hptp
// bentleyOttmann
//
#pragma once
#include "lineSegment.hpp"
#include "point2D.hpp"
#include <cstdio>
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:
};

View File

@ -0,0 +1,124 @@
//
// lineSegment.cpp
// bentleyOttmann
//
#include "lineSegment.hpp"
#include <algorithm>
#include <iostream>
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);
}
}

View File

@ -0,0 +1,54 @@
//
// lineSegment.hpp
// bentleyOttmann
//
#pragma once
#include "point2D.hpp"
#include <string>
#include <utility>
class LineSegment {
public:
LineSegment();
LineSegment(Point2D p1, Point2D p2, const char* groupId);
~LineSegment();
Point2D begin;
Point2D end;
float crossingPointY;
std::pair<int, int> 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);
}

View File

@ -0,0 +1,88 @@
//
// main.cpp
// bentleyOttman
//
#include <algorithm>
#include <iostream>
#include <vector>
#include "bentleyOttmann.hpp"
#include "lineSegment.hpp"
#include "point2D.hpp"
#include "testData/dataSet.hpp"
int test() {
std::vector<LineSegment> edgeVec;
std::vector<LineSegment> edgeVec2;
std::string dataPathA = "./testData/DataA-Table_1.csv";
std::string dataPathB = "./testData/DataB-Table_1.csv";
std::vector<std::vector<std::string> > v1 = TestDataSet::getDataSet(dataPathA);
std::vector<std::vector<std::string> > 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<LineSegment*>::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;
}

View File

@ -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;
}

View File

@ -0,0 +1,28 @@
//
// point2D.hpp
// bentleyOttmann
//
#pragma once
#include "vector2D.hpp"
#include <utility>
class Point2D {
public:
Point2D();
Point2D(float x, float y, int index);
~Point2D();
float x{}, y{};
int index{};
std::pair<float, float> 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;
};

View File

@ -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
1 0 0.0660550594329834 0.0660550594329834 1 0.2830275297164917 0.0660550594329834
2 1 0.2830275297164917 0.0660550594329834 6 0.2830275297164917 0.2830275297164917
3 6 0.2830275297164917 0.2830275297164917 5 0.0660550594329834 0.2830275297164917
4 5 0.0660550594329834 0.2830275297164917 0 0.0660550594329834 0.0660550594329834
5 1 0.2830275297164917 0.0660550594329834 2 0.50 0.0660550594329834
6 2 0.50 0.0660550594329834 7 0.770054817199707 0.36120128631591797
7 7 0.770054817199707 0.36120128631591797 6 0.2830275297164917 0.2830275297164917
8 2 0.50 0.0660550594329834 3 0.7169724702835083 0.0660550594329834
9 3 0.7169724702835083 0.0660550594329834 8 0.7596127390861511 0.23564951121807098
10 8 0.7596127390861511 0.23564951121807098 7 0.770054817199707 0.36120128631591797
11 3 0.7169724702835083 0.0660550594329834 4 0.9292071461677551 0.11106419563293457
12 4 0.9292071461677551 0.11106419563293457 9 0.9339449405670166 0.2830275297164917
13 9 0.9339449405670166 0.2830275297164917 8 0.7596127390861511 0.23564951121807098
14 6 0.2830275297164917 0.2830275297164917 28 0.2830275297164917 0.5
15 28 0.2830275297164917 0.5 29 0.0660550594329834 0.5
16 29 0.0660550594329834 0.5 5 0.0660550594329834 0.2830275297164917
17 7 0.770054817199707 0.36120128631591797 27 0.5 0.5
18 27 0.5 0.5 28 0.2830275297164917 0.5
19 8 0.7596127390861511 0.23564951121807098 25 0.7169724702835083 0.5
20 25 0.7169724702835083 0.5 27 0.5 0.5
21 9 0.9339449405670166 0.2830275297164917 14 0.9339449405670166 0.5
22 14 0.9339449405670166 0.5 25 0.7169724702835083 0.5

View File

@ -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
1 10 -0.5314721465110779 0.6870385408401489 11 -0.5849046111106873 0.8497003316879272
2 11 -0.5849046111106873 0.8497003316879272 16 -0.29304349422454834 0.8890276551246643
3 15 -0.47460252046585083 0.8964254856109619 10 -0.5314721465110779 0.6870385408401489
4 11 -0.5849046111106873 0.8497003316879272 12 -0.1126982569694519 0.5732992887496948
5 12 -0.1126982569694519 0.5732992887496948 17 -0.055828630924224854 0.782686173915863
6 17 -0.055828630924224854 0.782686173915863 16 -0.29304349422454834 0.8890276551246643
7 12 -0.1126982569694519 0.5732992887496948 13 0.09668868780136108 0.5164296627044678
8 13 0.09668868780136108 0.5164296627044678 18 0.19084149599075317 0.6371393203735352
9 18 0.19084149599075317 0.6371393203735352 17 -0.055828630924224854 0.782686173915863
10 13 0.09668868780136108 0.5164296627044678 26 0.3060756325721741 0.45955994725227356
11 26 0.3060756325721741 0.45955994725227356 19 0.3629452586174011 0.6689468026161194
12 19 0.3629452586174011 0.6689468026161194 18 0.19084149599075317 0.6371393203735352
13 16 -0.29304349422454834 0.8890276551246643 21 -0.2083459496498108 1.0489426851272583
14 21 -0.2083459496498108 1.0489426851272583 20 -0.4177328944206238 1.105812430381775
15 20 -0.4177328944206238 1.105812430381775 15 -0.47460252046585083 0.8964254856109619
16 17 -0.055828630924224854 0.782686173915863 22 0.004851400852203369 0.9247608184814453
17 22 0.004851400852203369 0.9247608184814453 21 -0.2083459496498108 1.0489426851272583
18 18 0.19084149599075317 0.6371393203735352 23 0.21042793989181519 0.935203492641449
19 23 0.21042793989181519 0.935203492641449 22 0.004851400852203369 0.9247608184814453
20 19 0.3629452586174011 0.6689468026161194 24 0.4198148846626282 0.8783338665962219
21 24 0.4198148846626282 0.8783338665962219 23 0.21042793989181519 0.935203492641449

View File

@ -0,0 +1,30 @@
//
// dataSet.cpp
// bentleyOttman
//
#include "dataSet.hpp"
#include <fstream>
#include <iostream>
#include <sstream>
std::vector<std::vector<std::string> > 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<std::vector<std::string> > v;
while (std::getline(ifs, str)) {
std::istringstream stream(str);
std::string field;
std::vector<std::string> result;
while (std::getline(stream, field, ',')) {
result.push_back(field);
}
v.push_back(result);
}
return v;
}

View File

@ -0,0 +1,17 @@
//
// dataSet.hpp
// bentleyOttman
//
#ifndef dataSet_hpp
#define dataSet_hpp
#include <string>
#include <vector>
class TestDataSet {
public:
static std::vector<std::vector<std::string> > getDataSet(std::string path);
};
#endif /* dataSet_hpp */

View File

@ -0,0 +1,41 @@
//
// vector2D.cpp
// bentleyOttmann
//
#include "vector2D.hpp"
#include <complex>
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;
}

View File

@ -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;
};

View File

@ -0,0 +1,392 @@
#include <algorithm>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include <unordered_set>
#include "findUvOverlaps.hpp"
#include <maya/MArgDatabase.h>
#include <maya/MDagPath.h>
#include <maya/MFloatArray.h>
#include <maya/MFnMesh.h>
#include <maya/MFnPlugin.h>
#include <maya/MGlobal.h>
#include <maya/MTimer.h>
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<float> uVector;
std::vector<float> 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<LineSegment> 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<LineSegment> result;
BentleyOttmann b(shell.lines);
b.check(result);
pushToLineVector(result);
}
void FindUvOverlaps::pushToLineVector(std::vector<LineSegment>& 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<int>(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<UVShell> 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<int>(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<std::string> 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<unsigned int>(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<std::pair<unsigned int, unsigned int>> 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<unsigned int>(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<unsigned int, unsigned int> 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<std::vector<LineSegment>> edgeVector;
edgeVector.resize(nbUvShells);
MFloatArray uArray;
MFloatArray vArray;
fnMesh.getUVs(uArray, vArray);
// Setup uv shell objects
std::vector<UVShell> 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<int>(idA));
Point2D p2(uArray[idB], vArray[idB], static_cast<int>(idB));
// Create new lineSegment object
LineSegment line(p1, p2, dagPathChar);
auto shellIndex = static_cast<unsigned int>(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<std::mutex> lock(mtx);
elements.emplace_back(path);
MString &tempMstring = elements.back();
const char* tempChar = tempMstring.asChar();
return tempChar;
}

View File

@ -0,0 +1,57 @@
#pragma once
#include "bentleyOttmann/bentleyOttmann.hpp"
#include "bentleyOttmann/lineSegment.hpp"
#include <vector>
#include <thread>
#include <mutex>
#include <maya/MString.h>
#include <maya/MArgList.h>
#include <maya/MSyntax.h>
#include <maya/MPxCommand.h>
#include <maya/MSelectionList.h>
class UVShell {
float left, right, top, bottom;
public:
std::vector<LineSegment> lines;
void initAABB();
bool operator*(const UVShell& other) const;
UVShell operator&&(const UVShell& other) const;
};
class MStringVector {
private:
std::mutex mtx;
std::vector<MString> 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<std::vector<LineSegment> > finalResult;
std::vector<UVShell> shellVector;
MStatus init(int i);
void btoCheck(UVShell &shell);
void pushToLineVector(std::vector<LineSegment> &v);
void pushToShellVector(UVShell &shell);
static void timeIt(const std::string& text, double t);
};