Initial commit
This commit is contained in:
parent
c8aa77b549
commit
d13d83b1ff
65
.gitignore
vendored
65
.gitignore
vendored
@ -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
113
CMakeLists.txt
Normal 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
21
LICENSE.md
Normal 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.
|
42
README.md
42
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
|
||||
```
|
||||
|
BIN
images/UVsInNegative.png
Normal file
BIN
images/UVsInNegative.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
images/concaveUVs.png
Normal file
BIN
images/concaveUVs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
images/reversedUVs.png
Normal file
BIN
images/reversedUVs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
BIN
images/udimIntersections.png
Normal file
BIN
images/udimIntersections.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
images/unMappedFaces.png
Normal file
BIN
images/unMappedFaces.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
images/zeroAreaUVFaces.png
Normal file
BIN
images/zeroAreaUVFaces.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
98
include/ThreadPool.hpp
Normal file
98
include/ThreadPool.hpp
Normal 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
61
include/utils.hpp
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
15
meshChecker/CMakeLists.txt
Normal file
15
meshChecker/CMakeLists.txt
Normal 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
35
meshChecker/README.md
Normal 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]']
|
||||
```
|
723
meshChecker/src/meshChecker.cpp
Normal file
723
meshChecker/src/meshChecker.cpp
Normal 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;
|
||||
}
|
36
meshChecker/src/meshChecker.hpp
Normal file
36
meshChecker/src/meshChecker.hpp
Normal 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
15
uvChecker/CMakeLists.txt
Normal 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
50
uvChecker/README.md
Normal 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]', ...]
|
||||
```
|
109
uvChecker/python/removeUnassginedUVs.py
Normal file
109
uvChecker/python/removeUnassginedUVs.py
Normal 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
545
uvChecker/src/uvChecker.cpp
Normal 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;
|
||||
}
|
36
uvChecker/src/uvChecker.hpp
Normal file
36
uvChecker/src/uvChecker.hpp
Normal 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;
|
||||
};
|
34
uvOverlapChecker/CMakeLists.txt
Normal file
34
uvOverlapChecker/CMakeLists.txt
Normal 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})
|
30
uvOverlapChecker/README.md
Normal file
30
uvOverlapChecker/README.md
Normal 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;"/>
|
263
uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.cpp
Normal file
263
uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.cpp
Normal 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;
|
||||
}
|
34
uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.hpp
Normal file
34
uvOverlapChecker/src/bentleyOttmann/bentleyOttmann.hpp
Normal 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;
|
||||
};
|
32
uvOverlapChecker/src/bentleyOttmann/event.cpp
Normal file
32
uvOverlapChecker/src/bentleyOttmann/event.cpp
Normal 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;
|
||||
}
|
34
uvOverlapChecker/src/bentleyOttmann/event.hpp
Normal file
34
uvOverlapChecker/src/bentleyOttmann/event.hpp
Normal 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:
|
||||
};
|
124
uvOverlapChecker/src/bentleyOttmann/lineSegment.cpp
Normal file
124
uvOverlapChecker/src/bentleyOttmann/lineSegment.cpp
Normal 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);
|
||||
}
|
||||
}
|
54
uvOverlapChecker/src/bentleyOttmann/lineSegment.hpp
Normal file
54
uvOverlapChecker/src/bentleyOttmann/lineSegment.hpp
Normal 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);
|
||||
}
|
88
uvOverlapChecker/src/bentleyOttmann/main.cpp
Normal file
88
uvOverlapChecker/src/bentleyOttmann/main.cpp
Normal 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;
|
||||
}
|
43
uvOverlapChecker/src/bentleyOttmann/point2D.cpp
Normal file
43
uvOverlapChecker/src/bentleyOttmann/point2D.cpp
Normal 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;
|
||||
}
|
28
uvOverlapChecker/src/bentleyOttmann/point2D.hpp
Normal file
28
uvOverlapChecker/src/bentleyOttmann/point2D.hpp
Normal 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;
|
||||
};
|
@ -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
|
|
@ -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
|
|
30
uvOverlapChecker/src/bentleyOttmann/testData/dataSet.cpp
Normal file
30
uvOverlapChecker/src/bentleyOttmann/testData/dataSet.cpp
Normal 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;
|
||||
}
|
17
uvOverlapChecker/src/bentleyOttmann/testData/dataSet.hpp
Normal file
17
uvOverlapChecker/src/bentleyOttmann/testData/dataSet.hpp
Normal 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 */
|
41
uvOverlapChecker/src/bentleyOttmann/vector2D.cpp
Normal file
41
uvOverlapChecker/src/bentleyOttmann/vector2D.cpp
Normal 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;
|
||||
}
|
19
uvOverlapChecker/src/bentleyOttmann/vector2D.hpp
Normal file
19
uvOverlapChecker/src/bentleyOttmann/vector2D.hpp
Normal 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;
|
||||
};
|
392
uvOverlapChecker/src/findUvOverlaps.cpp
Normal file
392
uvOverlapChecker/src/findUvOverlaps.cpp
Normal 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;
|
||||
}
|
57
uvOverlapChecker/src/findUvOverlaps.hpp
Normal file
57
uvOverlapChecker/src/findUvOverlaps.hpp
Normal 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);
|
||||
};
|
Loading…
Reference in New Issue
Block a user