From db9477dd217b987b90cb28a1c227ee3397893977 Mon Sep 17 00:00:00 2001 From: Grant Karapetyan Date: Thu, 15 Sep 2022 20:04:09 +0300 Subject: [PATCH] Update python bindings step 3 --- source/MRMesh/MRICP.h | 9 +- source/mrmeshpy/MRPythonMeshExposing.cpp | 42 +++ source/mrmeshpy/MRPythonMeshICPExposing.cpp | 106 +++--- source/mrmeshpy/MRPythonMeshPlugins.cpp | 350 ++++++++++---------- test_python/test_boolean.py | 28 +- test_python/test_collision.py | 4 +- test_python/test_deleteFaces.py | 2 +- test_python/test_distanceMap.py | 2 +- test_python/test_fillHole.py | 2 +- test_python/test_findMaxDistanceSqOneWay.py | 2 +- test_python/test_findMaxMeshDistanceSq.py | 2 +- test_python/test_fixSpikes.py | 4 +- test_python/test_fixTunnels.py | 2 +- test_python/test_fixUndercuts.py | 2 +- test_python/test_make_cube.py | 2 +- test_python/test_positionVertsSmooth.py | 2 +- test_python/test_signedDistance.py | 4 +- test_python/test_stitchTwoHoles.py | 6 +- test_python/test_subdivider.py | 2 +- 19 files changed, 306 insertions(+), 267 deletions(-) diff --git a/source/MRMesh/MRICP.h b/source/MRMesh/MRICP.h index d18881b2cf35..762da226962c 100644 --- a/source/MRMesh/MRICP.h +++ b/source/MRMesh/MRICP.h @@ -76,10 +76,9 @@ class MeshICP { public: // xf parameters should represent current transformations of meshes - // initMeshXf transform from the local mesh basis to the global. - // refMeshXf transform from the local refMesh basis to the global - // calculateTransform returns new mesh transformation to the global frame, which matches refMesh in the global frame - // bitset allows to take exact set of vertices from the mesh + // fltMeshXf transform from the local floatingMesh basis to the global + // refMeshXf transform from the local referenceMesh basis to the global + // floatingMeshBitSet allows to take exact set of vertices from the mesh MRMESH_API MeshICP(const MeshPart& floatingMesh, const MeshPart& referenceMesh, const AffineXf3f& fltMeshXf, const AffineXf3f& refMeshXf, const VertBitSet& floatingMeshBitSet); MRMESH_API MeshICP(const MeshPart& floatingMesh, const MeshPart& referenceMesh, const AffineXf3f& fltMeshXf, const AffineXf3f& refMeshXf, @@ -106,7 +105,7 @@ class MeshICP const std::vector& getVertPairs() const { return vertPairs_; } // used to visualize generated points pairs MRMESH_API std::pair getDistLimitsSq() const; // finds squared minimum and maximum pairs distances - //returns new xf transformation for the floating mesh, which allows to match reference mesh + // returns new xf transformation for the floating mesh, which allows to match reference mesh MRMESH_API AffineXf3f calculateTransformation(); private: diff --git a/source/mrmeshpy/MRPythonMeshExposing.cpp b/source/mrmeshpy/MRPythonMeshExposing.cpp index 42450ae845e0..ea6e93d567f1 100644 --- a/source/mrmeshpy/MRPythonMeshExposing.cpp +++ b/source/mrmeshpy/MRPythonMeshExposing.cpp @@ -56,6 +56,7 @@ MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, MeshTopology, [] ( pybind11::module_& m ) def( "findBoundaryFaces", &MR::MeshTopology::findBoundaryFaces, "returns all boundary faces, having at least one boundary edge" ). def( "findBoundaryEdges", &MR::MeshTopology::findBoundaryEdges, "returns all boundary edges, where each edge does not have valid left face" ). def( "findBoundaryVerts", &MR::MeshTopology::findBoundaryVerts, "returns all boundary vertices, incident to at least one boundary edge" ). + def( "deleteFaces", &MR::MeshTopology::deleteFaces, pybind11::arg( "fs" ), "deletes multiple given faces" ). def( "findBoundary", &MR::MeshTopology::findBoundary, pybind11::arg( "region" ) = nullptr, "returns all boundary loops, where each edge has region face to the right and does not have valid or in-region left face;\n" "unlike findRegionBoundary this method returns loops in opposite orientation" ). @@ -237,6 +238,10 @@ MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, FillHole, [] ( pybind11::module_& m ) m.def( "getEdgeLengthFillMetric", &MR::getEdgeLengthFillMetric, pybind11::arg( "mesh" ), "Simple metric minimizing the sum of all edge lengths" ); + m.def( "getEdgeLengthStitchMetric", &MR::getEdgeLengthStitchMetric, pybind11::arg( "mesh" ), + "Forbids connecting vertices from the same hole\n" + "Simple metric minimizing edge length" ); + m.def( "getCircumscribedMetric", &MR::getCircumscribedMetric, pybind11::arg( "mesh" ), "This metric minimizes the sum of circumcircle radii for all triangles in the triangulation.\n" "It is rather fast to calculate, and it results in typically good triangulations." ); @@ -251,6 +256,13 @@ MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, FillHole, [] ( pybind11::module_& m ) def_readwrite( "maxPolygonSubdivisions", &MR::FillHoleParams::maxPolygonSubdivisions, "The maximum number of polygon subdivisions on a triangle and two smaller polygons,\n""must be 2 or larger" ). def_readwrite( "outNewFaces", &MR::FillHoleParams::outNewFaces, "If not nullptr accumulate new faces" ); + pybind11::class_( m, "StitchHolesParams", "Structure has some options to control buildCylinderBetweenTwoHoles" ). + def( pybind11::init<>() ). + def_readwrite( "metric", &StitchHolesParams::metric, + "Specifies triangulation metric\n" + "default for buildCylinderBetweenTwoHoles: getComplexStitchMetric"). + def_readwrite( "outNewFaces", &StitchHolesParams::outNewFaces, "If not nullptr accumulate new faces" ); + m.def( "fillHole", &MR::fillHole, pybind11::arg( "mesh" ), pybind11::arg( "a" ), pybind11::arg( "params" ) = MR::FillHoleParams{}, "Fills given hole represented by one of its edges (having no valid left face),\n" @@ -259,6 +271,19 @@ MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, FillHole, [] ( pybind11::module_& m ) "\tmesh - mesh with hole\n" "\ta - EdgeId which represents hole\n" "\tparams - parameters of hole filling" ); + + m.def( "buildCylinderBetweenTwoHoles", ( void ( * )( Mesh&, EdgeId, EdgeId, const StitchHolesParams& ) )& MR::buildCylinderBetweenTwoHoles, + pybind11::arg( "mesh" ), pybind11::arg( "a" ), pybind11::arg( "b" ), pybind11::arg( "params" ) = MR::StitchHolesParams{}, + "Build cylindrical patch to fill space between two holes represented by one of their edges each,\n" + "default metric: ComplexStitchMetric\n" + "\tmesh - mesh with hole\n" + "\ta - EdgeId which represents 1st hole\n" + "\tb - EdgeId which represents 2nd hole\n" + "\tparams - parameters of holes stitching" ); + + m.def( "buildCylinderBetweenTwoHoles", ( bool ( * )( Mesh&, const StitchHolesParams& ) )& MR::buildCylinderBetweenTwoHoles, + pybind11::arg( "mesh" ), pybind11::arg( "params" ) = MR::StitchHolesParams{}, + "this version finds holes in the mesh by itself and returns false if they are not found" ); } ) Mesh pythonMergeMehses( const pybind11::list& meshes ) @@ -345,5 +370,22 @@ MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, SimpleFunctions, [] ( pybind11::module_& m ) "creates torus with empty sectors\n" "main application - testing Components" ); + + pybind11::class_( m, "FaceFace" ). + def( pybind11::init<>() ). + def( pybind11::init(), pybind11::arg( "a" ), pybind11::arg( "b" ) ). + def_readwrite( "aFace", &MR::FaceFace::aFace ). + def_readwrite( "bFace", &MR::FaceFace::bFace ); + + m.def( "findSelfCollidingTriangles", &MR::findSelfCollidingTriangles, pybind11::arg( "mp" ), "finds all pairs of colliding triangles from one mesh or a region" ); + m.def( "findSelfCollidingTrianglesBS", &MR::findSelfCollidingTrianglesBS, pybind11::arg( "mp" ), "finds union of all self-intersecting faces" ); + + m.def( "findCollidingTriangles", &MR::findCollidingTriangles, + pybind11::arg( "a" ), pybind11::arg( "b" ), pybind11::arg( "rigidB2A" ) = nullptr, pybind11::arg( "firstIntersectionOnly" ) = false, + "finds all pairs of colliding triangles from two meshes or two mesh regions\n" + "\trigidB2A - rigid transformation from B-mesh space to A mesh space, nullptr considered as identity transformation\n" + "\tfirstIntersectionOnly - if true then the function returns at most one pair of intersecting triangles and returns faster" ); } ) + +MR_ADD_PYTHON_VEC( mrmeshpy, vectorFaceFace, MR::FaceFace ) diff --git a/source/mrmeshpy/MRPythonMeshICPExposing.cpp b/source/mrmeshpy/MRPythonMeshICPExposing.cpp index 978703b85f2c..e5800cc9c1d6 100644 --- a/source/mrmeshpy/MRPythonMeshICPExposing.cpp +++ b/source/mrmeshpy/MRPythonMeshICPExposing.cpp @@ -21,59 +21,75 @@ bool operator == ( const VertPair& a, const VertPair& b ) MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, ICPExposing, [] ( pybind11::module_& m ) { pybind11::enum_( m, "ICPMethod" ). - value( "Combined", MR::ICPMethod::Combined ). - value( "PointToPoint", MR::ICPMethod::PointToPoint ). - value( "PointToPlane", MR::ICPMethod::PointToPlane ); + value( "Combined", MR::ICPMethod::Combined, "PointToPoint for the first 2 iterations, PointToPlane then" ). + value( "PointToPoint", MR::ICPMethod::PointToPoint, "use it in the cases with big differences, takes more iterations" ). + value( "PointToPlane", MR::ICPMethod::PointToPlane, "finds solution faster in fewer iterations" ); - pybind11::enum_( m, "ICPMode" ). - value( "AnyRigidXf", MR::ICPMode::AnyRigidXf ). - value( "OrthogonalAxis", MR::ICPMode::OrthogonalAxis ). - value( "FixedAxis", MR::ICPMode::FixedAxis ). - value( "TranslationOnly", MR::ICPMode::TranslationOnly ); + pybind11::enum_( m, "ICPMode", "You could fix any axis(axes) of rotation by using this modes" ). + value( "AnyRigidXf", MR::ICPMode::AnyRigidXf, "all 6 degrees of freedom (dof)" ). + value( "OrthogonalAxis", MR::ICPMode::OrthogonalAxis, "5 dof, except argument axis" ). + value( "FixedAxis", MR::ICPMode::FixedAxis, "4 dof, translation and one argument axis" ). + value( "TranslationOnly", MR::ICPMode::TranslationOnly, "3 dof, no rotation" ); - pybind11::class_( m, "ICPVertPair" ). + pybind11::class_( m, "VertPair" ). def( pybind11::init<>() ). - def_readwrite( "refPoint", &MR::VertPair::refPoint ). - def_readwrite( "norm", &MR::VertPair::norm ). - def_readwrite( "normRef", &MR::VertPair::normRef ). - def_readwrite( "vertId", &MR::VertPair::vertId ). - def_readwrite( "normalsAngleCos", &MR::VertPair::normalsAngleCos ). - def_readwrite( "vertDist2", &MR::VertPair::vertDist2 ). - def_readwrite( "weight", &MR::VertPair::weight ); + def_readwrite( "refPoint", &MR::VertPair::refPoint, "coordinates of the closest point on reference mesh (after applying refXf)" ). + def_readwrite( "norm", &MR::VertPair::norm, "surface normal in a vertex on the floating mesh (after applying Xf)" ). + def_readwrite( "normRef", &MR::VertPair::normRef, "surface normal in a vertex on the reference mesh (after applying Xf)" ). + def_readwrite( "vertId", &MR::VertPair::vertId, "ID of the floating mesh vertex (usually applying Xf required)" ). + def_readwrite( "normalsAngleCos", &MR::VertPair::normalsAngleCos, + "This is cosine between normals in first(floating mesh) and second(reference mesh) points\n" + "It evaluates how good is this pair" ). + def_readwrite( "vertDist2", &MR::VertPair::vertDist2, "Storing squared distance between vertices" ). + def_readwrite( "weight", &MR::VertPair::weight, "weight of the pair with respect to the sum of adjoining triangles square" ); pybind11::class_( m, "ICPProperties" ). def( pybind11::init<>() ). def_readwrite( "method", &MR::ICPProperties::method ). - def_readwrite( "p2plAngleLimit", &MR::ICPProperties::p2plAngleLimit ). - def_readwrite( "cosTreshold", &MR::ICPProperties::cosTreshold ). - def_readwrite( "distTresholdSq", &MR::ICPProperties::distTresholdSq ). - def_readwrite( "distStatisticSigmaFactor ", &MR::ICPProperties::distStatisticSigmaFactor ). - def_readwrite( "icpMode", &MR::ICPProperties::icpMode ). - def_readwrite( "fixedRotationAxis", &MR::ICPProperties::fixedRotationAxis ). - def_readwrite( "freezePairs", &MR::ICPProperties::freezePairs ). - def_readwrite( "iterLimit", &MR::ICPProperties::iterLimit ). - def_readwrite( "badIterStopCount", &MR::ICPProperties::badIterStopCount ). - def_readwrite( "exitVal", &MR::ICPProperties::exitVal ); + def_readwrite( "p2plAngleLimit", &MR::ICPProperties::p2plAngleLimit, + "rotation part will be limited by this value. If the whole rotation exceed this value, it will be normalized to that.\n" + "Note: PointToPlane only!"). + def_readwrite( "cosTreshold", &MR::ICPProperties::cosTreshold, "Points pair will be counted only if cosine between surface normals in points is higher" ). + def_readwrite( "distTresholdSq", &MR::ICPProperties::distTresholdSq, "Points pair will be counted only if squared distance between points is lower than" ). + def_readwrite( "distStatisticSigmaFactor ", &MR::ICPProperties::distStatisticSigmaFactor, + "Sigma multiplier for statistic throw of paints pair based on the distance\n" + "Default: all pairs in the interval the (distance = mean +- 3*sigma) are passed" ). + def_readwrite( "icpMode", &MR::ICPProperties::icpMode, "Finds only translation. Rotation part is identity matrix" ). + def_readwrite( "fixedRotationAxis", &MR::ICPProperties::fixedRotationAxis, "If this vector is not zero then rotation is allowed relative to this axis only" ). + def_readwrite( "freezePairs", &MR::ICPProperties::freezePairs, "keep point pairs from first iteration" ). + def_readwrite( "iterLimit", &MR::ICPProperties::iterLimit, "maximum iterations" ). + def_readwrite( "badIterStopCount", &MR::ICPProperties::badIterStopCount, "maximum iterations without improvements" ). + def_readwrite( "exitVal", &MR::ICPProperties::exitVal, "Algorithm target root-mean-square distance. As soon as it is reached, the algorithm stops." ); - pybind11::class_( m, "MeshICP" ). - def( pybind11::init() ). - def( pybind11::init() ). - def( "setParams", &MR::MeshICP::setParams ). - def( "setCosineLimit", &MR::MeshICP::setCosineLimit ). - def( "setDistanceLimit", &MR::MeshICP::setDistanceLimit ). - def( "setBadIterCount", &MR::MeshICP::setBadIterCount ). - def( "setPairsWeight", &MR::MeshICP::setPairsWeight ). - def( "setDistanceFilterSigmaFactor", &MR::MeshICP::setDistanceFilterSigmaFactor ). - def( "recomputeBitSet", &MR::MeshICP::recomputeBitSet ). - def( "getParams", &MR::MeshICP::getParams,pybind11::return_value_policy::copy ). - def( "getShiftVector", &MR::MeshICP::getShiftVector ). - def( "getLastICPInfo", &MR::MeshICP::getLastICPInfo ). - def( "getMeanSqDistToPoint", &MR::MeshICP::getMeanSqDistToPoint ). - def( "getMeanSqDistToPlane", &MR::MeshICP::getMeanSqDistToPlane ). - def( "getVertPairs", &MR::MeshICP::getVertPairs, pybind11::return_value_policy::copy ). - def( "getDistLimitsSq", &MR::MeshICP::getDistLimitsSq ). - def( "calculateTransformation", &MR::MeshICP::calculateTransformation ). - def( "updateVertPairs", &MR::MeshICP::updateVertPairs ); + pybind11::class_( m, "MeshICP", "This class allows to match two meshes with almost same geometry throw ICP point-to-point or point-to-plane algorithms" ). + def( pybind11::init(), + pybind11::arg("floatingMesh"), pybind11::arg( "referenceMesh" ), pybind11::arg( "fltMeshXf" ), pybind11::arg( "refMeshXf" ), pybind11::arg( "floatingMeshBitSet" ), + "xf parameters should represent current transformations of meshes\n" + "fltMeshXf - transform from the local floatingMesh basis to the global\n" + "refMeshXf - transform from the local referenceMesh basis to the global\n" + "floatingMeshBitSet - allows to take exact set of vertices from the mesh" ). + def( pybind11::init(), + pybind11::arg( "floatingMesh" ), pybind11::arg( "referenceMesh" ), pybind11::arg( "fltMeshXf" ), pybind11::arg( "refMeshXf" ), pybind11::arg( "floatSamplingVoxelSize" ), + "xf parameters should represent current transformations of meshes\n" + "fltMeshXf - transform from the local floatingMesh basis to the global\n" + "refMeshXf - transform from the local referenceMesh basis to the global\n" + "floatSamplingVoxelSize = positive value here defines voxel size, and only one vertex per voxel will be selected" ). + def( "setParams", &MR::MeshICP::setParams, pybind11::arg( "prop" ), "tune algirithm params before run calculateTransformation()" ). + def( "setCosineLimit", &MR::MeshICP::setCosineLimit, pybind11::arg( "cos" ) ). + def( "setDistanceLimit", &MR::MeshICP::setDistanceLimit, pybind11::arg( "dist" ) ). + def( "setBadIterCount", &MR::MeshICP::setBadIterCount, pybind11::arg( "iter" ) ). + def( "setPairsWeight", &MR::MeshICP::setPairsWeight, pybind11::arg( "v" ) ). + def( "setDistanceFilterSigmaFactor", &MR::MeshICP::setDistanceFilterSigmaFactor, pybind11::arg( "factor" ) ). + def( "recomputeBitSet", &MR::MeshICP::recomputeBitSet, pybind11::arg( "floatSamplingVoxelSize" ) ). + def( "getParams", &MR::MeshICP::getParams, pybind11::return_value_policy::copy ). + def( "getShiftVector", &MR::MeshICP::getShiftVector, "shows mean pair vector" ). + def( "getLastICPInfo", &MR::MeshICP::getLastICPInfo, "returns status info string" ). + def( "getMeanSqDistToPoint", &MR::MeshICP::getMeanSqDistToPoint, "computes root-mean-square deviation between points" ). + def( "getMeanSqDistToPlane", &MR::MeshICP::getMeanSqDistToPlane, "computes root-mean-square deviation from points to target planes" ). + def( "getVertPairs", &MR::MeshICP::getVertPairs, pybind11::return_value_policy::copy, "used to visualize generated points pairs" ). + def( "getDistLimitsSq", &MR::MeshICP::getDistLimitsSq, "finds squared minimum and maximum pairs distances" ). + def( "calculateTransformation", &MR::MeshICP::calculateTransformation, "returns new xf transformation for the floating mesh, which allows to match reference mesh" ). + def( "updateVertPairs", &MR::MeshICP::updateVertPairs, "recompute point pairs after manual change of transformations or parameters" ); } ) MR_ADD_PYTHON_VEC( mrmeshpy, vectorICPVertPair, MR::VertPair ) diff --git a/source/mrmeshpy/MRPythonMeshPlugins.cpp b/source/mrmeshpy/MRPythonMeshPlugins.cpp index 1abccba4ffb5..f7384e554278 100644 --- a/source/mrmeshpy/MRPythonMeshPlugins.cpp +++ b/source/mrmeshpy/MRPythonMeshPlugins.cpp @@ -36,228 +36,226 @@ void fixSelfIntersections( Mesh& mesh1, float voxelSize ) auto gridA = convert(mesh1); mesh1 = convert(gridA); } -MR_ADD_PYTHON_FUNCTION( mrmeshpy, fix_self_intersections, &fixSelfIntersections, "subtract second mesh from the first one" ) // Boolean -void booleanSub( Mesh& mesh1, const Mesh& mesh2, float voxelSize ) +Mesh booleanSub( const Mesh& mesh1, const Mesh& mesh2, float voxelSize ) { MeshVoxelsConverter convert; convert.voxelSize = voxelSize; auto gridA = convert(mesh1); auto gridB = convert(mesh2); gridA -= gridB; - mesh1 = convert(gridA); + return convert(gridA); } -MR_ADD_PYTHON_FUNCTION( mrmeshpy, boolean_sub, &booleanSub, "subtract second mesh from the first one" ) -void booleanUnion( Mesh& mesh1, const Mesh& mesh2, float voxelSize ) +Mesh booleanUnion( const Mesh& mesh1, const Mesh& mesh2, float voxelSize ) { MeshVoxelsConverter convert; convert.voxelSize = voxelSize; auto gridA = convert(mesh1); auto gridB = convert(mesh2); gridA += gridB; - mesh1 = convert(gridA); + return convert( gridA ); } -MR_ADD_PYTHON_FUNCTION( mrmeshpy, boolean_union, &booleanUnion, "merge second mesh into the first one" ) -void booleanIntersect( Mesh& mesh1, const Mesh& mesh2, float voxelSize ) +Mesh booleanIntersect( const Mesh& mesh1, const Mesh& mesh2, float voxelSize ) { MeshVoxelsConverter convert; convert.voxelSize = voxelSize; auto gridA = convert(mesh1); auto gridB = convert(mesh2); gridA *= gridB; - mesh1 = convert(gridA); + return convert( gridA ); } -MR_ADD_PYTHON_FUNCTION( mrmeshpy, boolean_intersect, &booleanIntersect, "stores intersection of two meshes into the first one" ) -// Stitch two Holes -void pythonSetStitchHolesEdgeLengthMetric( MR::StitchHolesParams& params, const Mesh& mesh ) +MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, VoxelBooleanBlock, [] ( pybind11::module_& m ) { - params.metric = getEdgeLengthStitchMetric( mesh ); -} + m.def( "fixSelfIntersections", &fixSelfIntersections, pybind11::arg( "mesh" ), pybind11::arg( "voxelSize" ), "fix self-intersections by converting to voxels and back" ); + m.def( "voxelBooleanSubtract", &booleanSub, pybind11::arg( "meshA" ), pybind11::arg( "meshB" ), pybind11::arg( "voxelSize" ), "subtract mesh B from mesh A" ); + m.def( "voxelBooleanUnite", &booleanUnion, pybind11::arg( "meshA" ), pybind11::arg( "meshB" ), pybind11::arg( "voxelSize" ), "unite mesh A and mesh B" ); + m.def( "voxelBooleanIntersect", &booleanIntersect, pybind11::arg( "meshA" ), pybind11::arg( "meshB" ), pybind11::arg( "voxelSize" ), "intersect mesh A and mesh B" ); -MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, StitchHolesParams, [] ( pybind11::module_& m ) -{ - pybind11::class_( m, "StitchHolesParams" ). - def( pybind11::init<>() ). - def_readwrite( "outNewFaces", &StitchHolesParams::outNewFaces); - - m.def( "set_stitch_holes_metric_edge_length", pythonSetStitchHolesEdgeLengthMetric, "set edge length metric to stitch holes parameters" ); } ) -MR_ADD_PYTHON_FUNCTION( mrmeshpy, stitch_holes, - static_cast (&buildCylinderBetweenTwoHoles), - "stitches two holes with presented edges on the mesh" ) - -MR_ADD_PYTHON_FUNCTION( mrmeshpy, stitch_two_holes, - static_cast (&buildCylinderBetweenTwoHoles), - "stitches holes on the mesh with exact two holes" ) - -// Fix Tunnels -MR_ADD_PYTHON_FUNCTION( mrmeshpy, get_tunnel_faces, &MR::detectTunnelFaces, - "returns tunnel faces. Remove them and stitch new holes to fill tunnels. Tunnel length is the treshold for big holes" ) - -// Homology Basis -MR_ADD_PYTHON_FUNCTION( mrmeshpy, detect_basis_tunnels, &MR::detectBasisTunnels, "finds homology basis as the set of edge loops" ) - -// Text Mesh -MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, SymbolMeshParams, [] ( pybind11::module_& m ) +MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, DegenerationsDetection, [] ( pybind11::module_& m ) { - pybind11::class_( m, "SymbolMeshParams" ). - def( pybind11::init<>() ). - def_readwrite( "text", &SymbolMeshParams::text ). - def_readwrite( "fontDetalization", &SymbolMeshParams::fontDetalization ). - def_readwrite( "symbolsDistanceAdditionalOffset", &SymbolMeshParams::symbolsDistanceAdditionalOffset ). - def_readwrite( "pathToFontFile", &TextMeshAlignParams::pathToFontFile ); + m.def( "detectTunnelFaces", &MR::detectTunnelFaces, pybind11::arg( "mp" ), pybind11::arg( "maxTunnelLength" ), + "returns tunnels as a number of faces;\n" + "if you remove these faces and patch every boundary with disk, then the surface will be topology equivalent to sphere" ); + + m.def( "detectBasisTunnels", &MR::detectBasisTunnels, pybind11::arg( "mp" ), "detects all not-contractible-in-point and not-equivalent tunnel loops on the mesh" ); + + m.def( "findDegenerateFaces", &MR::findDegenerateFaces, + pybind11::arg( "mesh" ), pybind11::arg( "criticalAspectRatio" ) = FLT_MAX, + "finds faces which aspect ratio >= criticalAspectRatio" ); + + m.def( "fixMultipleEdges", ( void( * )( MR::Mesh& ) )& MR::fixMultipleEdges, + pybind11::arg( "mesh" ), "finds and resolves multiple edges" ); + + m.def( "hasMultipleEdges", &MR::hasMultipleEdges, + pybind11::arg( "topology" ), "finds multiple edges in the mesh" ); + + m.def( "removeSpikes", &MR::removeSpikes, + pybind11::arg( "mesh" ), pybind11::arg( "maxIterations" ), pybind11::arg( "minSumAngle" ), pybind11::arg( "region" ) = nullptr, + "applies at most given number of relaxation iterations the spikes detected by given threshold" ); + + m.def( "fixUndercuts", ( void ( * )( Mesh&, const FaceBitSet&, const Vector3f&, float, float ) )& MR::FixUndercuts::fixUndercuts, + pybind11::arg( "mesh" ), pybind11::arg( "selectedArea" ), pybind11::arg( "upDirection" ), pybind11::arg( "voxelSize" ) = 0.0f, pybind11::arg( "bottomExtension" ) = 0.0f, + "aChanges mesh:\n" + "Fills all holes first, then:\n" + "fixes undercuts (in selected area) via prolonging widest points down\n" + "Requires to update RenderObject after using\n" + "upDirection is in mesh space\n" + "voxelSize - size of voxel in mesh rasterization, precision grows with lower voxelSize\n" + "bottomExtension - this parameter specifies how long should bottom prolongation be, if (bottomExtension <= 0) bottomExtension = 2*voxelSize\n" + "\tif mesh is not closed this is used to prolong hole and make bottom\n" + "\nif voxelSize == 0.0f it will be counted automaticly" ); + + m.def( "fixUndercuts", ( void ( * )( Mesh&, const Vector3f&, float, float ) )& MR::FixUndercuts::fixUndercuts, + pybind11::arg( "mesh" ), pybind11::arg( "upDirection" ), pybind11::arg( "voxelSize" ) = 0.0f, pybind11::arg( "bottomExtension" ) = 0.0f, + "aChanges mesh:\n" + "Fills all holes first, then:\n" + "fixes undercuts via prolonging widest points down\n" + "Requires to update RenderObject after using\n" + "upDirection is in mesh space\n" + "voxelSize - size of voxel in mesh rasterization, precision grows with lower voxelSize\n" + "bottomExtension - this parameter specifies how long should bottom prolongation be, if (bottomExtension <= 0) bottomExtension = 2*voxelSize\n" + "\tif mesh is not closed this is used to prolong hole and make bottom\n" + "\nif voxelSize == 0.0f it will be counted automaticly" ); } ) -MR_ADD_PYTHON_FUNCTION( mrmeshpy, create_text_mesh, &createSymbolsMesh, "create text mesh. Use empty path for default" ) - -// Text on Mesh -MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, TextAlignParams, [] ( pybind11::module_& m ) -{ - pybind11::class_( m, "EdgeIdAndCoord" ). - def( pybind11::init<>() ). - def_readwrite( "id", &EdgeIdAndCoord::id ). - def_readwrite( "coord", &EdgeIdAndCoord::coord ); - - pybind11::class_( m, "TextAlignParams" ). - def( pybind11::init<>() ). - def_readwrite( "startPoint", &TextMeshAlignParams::startPoint ). - def_readwrite( "direction", &TextMeshAlignParams::direction ). - def_readwrite( "fontDetalization", &TextMeshAlignParams::fontDetalization ). - def_readwrite( "text", &TextMeshAlignParams::text ). - def_readwrite( "fontHeight ", &TextMeshAlignParams::fontHeight ). - def_readwrite( "surfaceOffset", &TextMeshAlignParams::surfaceOffset ). - def_readwrite( "textMaximumMovement", &TextMeshAlignParams::textMaximumMovement ). - def_readwrite( "symbolsDistanceAdditionalOffset", &TextMeshAlignParams::symbolsDistanceAdditionalOffset ). - def_readwrite( "pathToFontFile", &TextMeshAlignParams::pathToFontFile ); -} ) Mesh createTextOnMesh( Mesh& mesh, const AffineXf3f& xf, TextMeshAlignParams params ) { - if(params.pathToFontFile.empty()) + if ( params.pathToFontFile.empty() ) params.pathToFontFile = GetFontsDirectory().append( "Karla-Regular.ttf" ); auto res = alignTextToMesh( mesh, xf, params ); - if(res) + if ( res ) return res.value(); else // failed to align return {}; } -MR_ADD_PYTHON_FUNCTION( mrmeshpy, create_text_on_mesh, &createTextOnMesh, "create text on mesh" ) - -// Laplacian Brush -// nothing to add or test - -// Fix Undercuts -MR_ADD_PYTHON_FUNCTION( mrmeshpy, fix_undercuts_on_area, - static_cast (&MR::FixUndercuts::fixUndercuts), - "fill all undercuts in direction for fixed area" ) +// Text Mesh +MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, SymbolMeshParams, [] ( pybind11::module_& m ) +{ + pybind11::class_( m, "SymbolMeshParams" ). + def( pybind11::init<>() ). + def_readwrite( "text", &SymbolMeshParams::text, "Text that will be made mesh" ). + def_readwrite( "fontDetalization", &SymbolMeshParams::fontDetalization, "Detailization of Bezier curves on font glyphs" ). + def_readwrite( "symbolsDistanceAdditionalOffset", &SymbolMeshParams::symbolsDistanceAdditionalOffset, + "Additional offset between symbols (in symbol size: 1.0f adds one \"space\", 0.5 adds half \"space\")\n" + "should be >= 0.0f" ). + def_readwrite( "pathToFontFile", &TextMeshAlignParams::pathToFontFile, "Path to font file" ); -MR_ADD_PYTHON_FUNCTION( mrmeshpy, fix_undercuts, - static_cast (&MR::FixUndercuts::fixUndercuts), - "fill all undercuts in direction" ) + m.def( "createSymbolsMesh", &MR::createSymbolsMesh, pybind11::arg( "params" ), "converts text string into Z-facing symbol mesh" ); -// Free Form Transform -// nothing to add or test + pybind11::class_( m, "EdgeIdAndCoord", "This structure represents point on mesh, by EdgeId (point should be in left triangle of this edge) and coordinate" ). + def( pybind11::init<>() ). + def_readwrite( "id", &EdgeIdAndCoord::id ). + def_readwrite( "coord", &EdgeIdAndCoord::coord ); -// Collision Plugin -MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, FaceFace, [] ( pybind11::module_& m ) -{ - pybind11::class_( m, "FaceFace" ). + pybind11::class_( m, "TextAlignParams" ). def( pybind11::init<>() ). - def_readwrite( "aFace", &MR::FaceFace::aFace ). - def_readwrite( "bFace", &MR::FaceFace::bFace ); + def_readwrite( "startPoint", &TextMeshAlignParams::startPoint, "Start coordinate on mesh, represent lowest left corner of text" ). + def_readwrite( "direction", &TextMeshAlignParams::direction, "Direction of text" ). + def_readwrite( "fontHeight ", &TextMeshAlignParams::fontHeight, "Font height, meters" ). + def_readwrite( "surfaceOffset", &TextMeshAlignParams::surfaceOffset, "Text mesh inside and outside offset of input mesh" ). + def_readwrite( "textMaximumMovement", &TextMeshAlignParams::textMaximumMovement, "Maximum possible movement of text mesh alignment, meters" ); + + m.def( "alignTextToMesh", &createTextOnMesh, pybind11::arg( "mesh" ), pybind11::arg( "xf" ), pybind11::arg( "params" ), + "create text on mesh" ); } ) -MR_ADD_PYTHON_VEC( mrmeshpy, vectorFaceFace, MR::FaceFace ) -// TODO: introduce MeshPart -std::vector findCollidingTrianglesSimple( const Mesh& a, const Mesh& b, const AffineXf3f * rigidB2A, bool firstIntersectionOnly ) -{ - return findCollidingTriangles(a, b, rigidB2A, firstIntersectionOnly); -} -MR_ADD_PYTHON_FUNCTION( mrmeshpy, find_colliding_faces, &findCollidingTrianglesSimple, "finds all colliding face pairs" ) +MR_ADD_PYTHON_VEC( mrmeshpy, vectorMeshEdgePoint, MR::MeshEdgePoint ) // Signed Distance MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, MeshSignedDistanceResult, [] ( pybind11::module_& m ) { pybind11::class_( m, "MeshSignedDistanceResult" ). def( pybind11::init<>() ). - def_readwrite( "a", &MeshSignedDistanceResult::a ). - def_readwrite( "b", &MeshSignedDistanceResult::b ). - def_readwrite( "signedDist", &MeshSignedDistanceResult::signedDist ); + def_readwrite( "a", &MeshSignedDistanceResult::a, "two closest points: from meshes A and B respectively" ). + def_readwrite( "b", &MeshSignedDistanceResult::b, "two closest points: from meshes A and B respectively" ). + def_readwrite( "signedDist", &MeshSignedDistanceResult::signedDist, "signed distance between a and b, positive if meshes do not collide" ); + + m.def( "findSignedDistance", ( MeshSignedDistanceResult( * )( const MeshPart&, const MeshPart&, const AffineXf3f*, float ) )& MR::findSignedDistance, + pybind11::arg( "a" ), pybind11::arg( "b" ), pybind11::arg( "rigidB2A" ) = nullptr, pybind11::arg( "upDistLimitSq" ) = FLT_MAX, + "computes minimal distance between two meshes\n" + "\trigidB2A - rigid transformation from B-mesh space to A mesh space, nullptr considered as identity transformation\n" + "\tupDistLimitSq - upper limit on the positive distance in question, if the real distance is larger than the function exists returning upDistLimitSq and no valid points" ); + + m.def("findMaxDistanceSqOneWay",&MR::findMaxDistanceSqOneWay, + pybind11::arg( "a" ), pybind11::arg( "b" ), pybind11::arg( "rigidB2A" ) = nullptr, pybind11::arg( "upDistLimitSq" ) = FLT_MAX, + "returns the maximum of the distances from each B-mesh point to A-mesh\n" + "\trigidB2A - rigid transformation from B-mesh space to A mesh space, nullptr considered as identity transformation\n" + "\tmaxDistanceSq - upper limit on the positive distance in question, if the real distance is larger than the function exists returning maxDistanceSq" ); + + m.def( "findMaxDistanceSq", &MR::findMaxDistanceSq, + pybind11::arg( "a" ), pybind11::arg( "b" ), pybind11::arg( "rigidB2A" ) = nullptr, pybind11::arg( "upDistLimitSq" ) = FLT_MAX, + "returns the maximum of the distances from each mesh point to another mesh in both directions\n" + "\trigidB2A - rigid transformation from B-mesh space to A mesh space, nullptr considered as identity transformation\n" + "\tmaxDistanceSq - upper limit on the positive distance in question, if the real distance is larger than the function exists returning maxDistanceSq" ); } ) -MeshSignedDistanceResult findSignedDistanceSimple( const Mesh& a, const Mesh& b, const AffineXf3f& rigidB2A ) -{ - return findSignedDistance( a, b, &rigidB2A, std::numeric_limits::max() ); -} -MR_ADD_PYTHON_FUNCTION( mrmeshpy, find_signed_distance, &findSignedDistanceSimple, "finds signed distance for the current mesh. Negative value is for inner points" ) - -// Fix Spikes -MR_ADD_PYTHON_FUNCTION( mrmeshpy, remove_spikes, &removeSpikes, "removes spikes for the current mesh with given iterations" ) - -// Surface Distance -// nothing to add or test - -// Geodesic Path -MR_ADD_PYTHON_VEC( mrmeshpy, vectorMeshEdgePoint, MR::MeshEdgePoint ) - -MR_ADD_PYTHON_FUNCTION( mrmeshpy, compute_surface_path, &computeSurfacePath, "finds closest surface path between points" ) - // Relax Mesh MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, Relax, [] ( pybind11::module_& m ) { pybind11::class_( m, "RelaxParams" ). def( pybind11::init<>() ). - def_readwrite( "force", &RelaxParams::force ). - def_readwrite( "iterations", &RelaxParams::iterations ). - def_readwrite( "region", &RelaxParams::region ); + def_readwrite( "force", &RelaxParams::force, "speed of relaxing, typical values (0.0, 0.5]" ). + def_readwrite( "iterations", &RelaxParams::iterations, "number of iterations" ). + def_readwrite( "region", &RelaxParams::region, "region to relax" ); pybind11::class_( m, "MeshRelaxParams" ). def( pybind11::init<>() ). - def_readwrite( "hardSmoothTetrahedrons", &MeshRelaxParams::hardSmoothTetrahedrons ); + def_readwrite( "hardSmoothTetrahedrons", &MeshRelaxParams::hardSmoothTetrahedrons, "smooth tetrahedron verts (with complete three edges ring) to base triangle (based on its edges destinations)" ); m.def( "relax", [] ( Mesh& mesh, const MeshRelaxParams& params ) { - return relax( mesh, params ); + return relax( mesh, params ); // lambda to skip progress callback parameter }, pybind11::arg( "mesh" ), pybind11::arg( "params" ) = MeshRelaxParams{}, - "Relax mesh" ); + "applies given number of relaxation iterations to the whole mesh ( or some region if it is specified )\n" + "return true if was finished successfully, false if was interrupted by progress callback"); } ) -// Re-mesh -MR_ADD_PYTHON_FUNCTION( mrmeshpy, make_delone_edge_flips, &makeDeloneEdgeFlips, "Delone flips edges" ) - // Subdivider Plugin MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, SubdivideSettings, [] ( pybind11::module_& m ) { pybind11::class_( m, "SubdivideSettings" ). def( pybind11::init<>() ). - def_readwrite( "maxEdgeLen", &SubdivideSettings::maxEdgeLen ). - def_readwrite( "maxEdgeSplits", &SubdivideSettings::maxEdgeSplits ). - def_readwrite( "maxDeviationAfterFlip", &SubdivideSettings::maxDeviationAfterFlip ); + def_readwrite( "maxEdgeLen", &SubdivideSettings::maxEdgeLen, "Subdivision is stopped when all edges inside or on the boundary of the region are not longer than this value" ). + def_readwrite( "maxEdgeSplits", &SubdivideSettings::maxEdgeSplits, "Maximum number of edge splits allowed" ). + def_readwrite( "maxDeviationAfterFlip", &SubdivideSettings::maxDeviationAfterFlip, "Improves local mesh triangulation by doing edge flips if it does not make too big surface deviation" ). + def_readwrite( "maxAngleChangeAfterFlip", &SubdivideSettings::maxAngleChangeAfterFlip, "Improves local mesh triangulation by doing edge flips if it does change dihedral angle more than on this value" ). + def_readwrite( "region ", &SubdivideSettings::region, "Region on mesh to be subdivided, it is updated during the operation" ). + def_readwrite( "newVerts ", &SubdivideSettings::newVerts, "New vertices appeared during subdivision will be added here" ). + def_readwrite( "subdivideBorder", &SubdivideSettings::subdivideBorder, + "If false do not touch border edges (cannot subdivide lone faces)\n" + "use findRegionOuterFaces to find boundary faces" ). + def_readwrite( "critAspectRatio", &SubdivideSettings::critAspectRatio, + "If subdivideBorder is off subdivider can produce narrow triangles near border\n" + "this parameter prevents subdivision of such triangles" ). + def_readwrite( "useCurvature", &SubdivideSettings::useCurvature, + "This option works best for natural surfaces, where all triangles are close to equilateral and have similar area,\n" + "and no sharp edges in between" ). + def_readwrite( "newVerts ", &SubdivideSettings::newVerts, "New vertices appeared during subdivision will be added here" ); + + m.def( "subdivideMesh", &MR::subdivideMesh, + pybind11::arg( "mesh" ), pybind11::arg( "settings" ) = MR::SubdivideSettings{}, + "Split edges in mesh region according to the settings;\n" + "return The total number of edge splits performed" ); + } ) -// TODO: introduce MeshPart -MR_ADD_PYTHON_FUNCTION( mrmeshpy, subdivide_mesh, &subdivideMesh, "split edges in mesh with settings" ) -static void deleteFaces( MeshTopology& topology, const FaceBitSet& fs ) +void saveDistanceMapToImageSimple( const DistanceMap& dm, const std::string& filename ) { - topology.deleteFaces( fs ); + saveDistanceMapToImage( dm, filename ); } -MR_ADD_PYTHON_FUNCTION( mrmeshpy, delete_faces, &deleteFaces, "delete faces from topology" ) - -MR_ADD_PYTHON_FUNCTION( mrmeshpy, fix_multiple_edges, (void(*)(Mesh&))&fixMultipleEdges, "resolves multiple edges in the mesh" ) - -MR_ADD_PYTHON_FUNCTION( mrmeshpy, has_multiple_edges, hasMultipleEdges, "checks whether mesh topology has multiple edges" ) - // Distance Map MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, DistanceMap, [] ( pybind11::module_& m ) { @@ -276,57 +274,45 @@ MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, DistanceMap, [] ( pybind11::module_& m ) def( "set", static_cast< void( MR::DistanceMap::* )( size_t, size_t, float ) >( &MR::DistanceMap::set ), "write X,Y value" ). def( "unset", static_cast< void( MR::DistanceMap::* )( size_t, size_t ) >( &MR::DistanceMap::unset), "invalidate X,Y pixel" ). def( "unset", static_cast< void( MR::DistanceMap::* )( size_t ) >( &MR::DistanceMap::unset), "invalidate by index" ); -} ) -// MeshToDistanceMapParams -MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, MeshToDistanceMapParams, [] ( pybind11::module_& m ) -{ + pybind11::class_( m, "MeshToDistanceMapParams" ). - def( pybind11::init<>() ). - def( "setDistanceLimits", &MR::MeshToDistanceMapParams::setDistanceLimits ). - def_readwrite( "xRange", &MR::MeshToDistanceMapParams::xRange ). - def_readwrite( "yRange", &MR::MeshToDistanceMapParams::yRange ). - def_readwrite( "direction", &MR::MeshToDistanceMapParams::direction ). - def_readwrite( "orgPoint", &MR::MeshToDistanceMapParams::orgPoint ). - def_readwrite( "useDistanceLimits", &MR::MeshToDistanceMapParams::useDistanceLimits ). - def_readwrite( "allowNegativeValues", &MR::MeshToDistanceMapParams::allowNegativeValues ). - def_readwrite( "minValue", &MR::MeshToDistanceMapParams::minValue ). - def_readwrite( "maxValue", &MR::MeshToDistanceMapParams::maxValue ). - def_readwrite( "resolution", &MR::MeshToDistanceMapParams::resolution ); + def( pybind11::init<>(), "default constructor. Manual params initialization is required" ). + def( "setDistanceLimits", &MR::MeshToDistanceMapParams::setDistanceLimits, pybind11::arg( "min" ), pybind11::arg( "max" ), + "if distance is not in set range, pixel became invalid\n" + "default value: false. Any distance will be applied (include negative)" ). + def_readwrite( "xRange", &MR::MeshToDistanceMapParams::xRange, "Cartesian range vector between distance map borders in X direction" ). + def_readwrite( "yRange", &MR::MeshToDistanceMapParams::yRange, "Cartesian range vector between distance map borders in Y direction" ). + def_readwrite( "direction", &MR::MeshToDistanceMapParams::direction, "direction of intersection ray" ). + def_readwrite( "orgPoint", &MR::MeshToDistanceMapParams::orgPoint, "location of (0,0) pixel with value 0.f" ). + def_readwrite( "useDistanceLimits", &MR::MeshToDistanceMapParams::useDistanceLimits, "out of limits intersections will be set to non-valid" ). + def_readwrite( "allowNegativeValues", &MR::MeshToDistanceMapParams::allowNegativeValues, "allows to find intersections in backward to direction vector with negative values" ). + def_readwrite( "minValue", &MR::MeshToDistanceMapParams::minValue, "Using of this parameter depends on useDistanceLimits" ). + def_readwrite( "maxValue", &MR::MeshToDistanceMapParams::maxValue, "Using of this parameter depends on useDistanceLimits" ). + def_readwrite( "resolution", &MR::MeshToDistanceMapParams::resolution, "resolution of distance map" ); + + m.def( "computeDistanceMapD", &MR::computeDistanceMapD, pybind11::arg( "mp" ), pybind11::arg( "params" ), + "computes distance map for presented projection parameters\n" + "use MeshToDistanceMapParams constructor instead of overloads of this function\n" + "MeshPart - input 3d model\n" + "general call. You could customize params manually" ); + + m.def( "saveDistanceMapToImage", &saveDistanceMapToImageSimple, pybind11::arg( "distMap" ), pybind11::arg( "filename" ), + "saves distance map to monochrome image in scales of gray:\n" + "far: 0.3 (dark-gray)\n" + "close: 1.0 (white)\n" + "invalid (infinity): 0.0 (black)" ); } ) -// Subdivider Plugin - -MR::DistanceMap computeDMwithParams( const Mesh& mesh, const MeshToDistanceMapParams& params ) -{ - return computeDistanceMapD( mesh, params ); -} - -MR_ADD_PYTHON_FUNCTION( mrmeshpy, compute_distance_map, - &computeDMwithParams, - "computes Distance Map with given xf transformation, pixel size. Precise bounding box computation as the last parameter" ) - -MR_ADD_PYTHON_FUNCTION( mrmeshpy, get_distance_map_mesh, &distanceMapToMesh, - "converts distance map to mesh" ) - -// TODO: introduce filesystem::path -void saveDistanceMapToImageSimple(const DistanceMap& dm, const std::string& filename) -{ - saveDistanceMapToImage(dm, filename); -} -MR_ADD_PYTHON_FUNCTION( mrmeshpy, save_depth_image, &saveDistanceMapToImageSimple, "saves distance map to image file" ) - // Position Verts Smooth MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, LaplacianEdgeWeightsParam, [] ( pybind11::module_& m ) { pybind11::enum_( m, "LaplacianEdgeWeightsParam" ). - value( "Unit", Laplacian::EdgeWeights::Unit ). - value( "Cotan", Laplacian::EdgeWeights::Cotan ); -} ) - -MR_ADD_PYTHON_FUNCTION( mrmeshpy, position_verts_smoothly, &positionVertsSmoothly, "shifts vertices to make smooth surface by Unit Laplacian" ) + value( "Unit", Laplacian::EdgeWeights::Unit, "all edges have same weight=1" ). + value( "Cotan", Laplacian::EdgeWeights::Cotan, "edge weight depends on local geometry and uses cotangent values" ). + value( "CotanTimesLength", Laplacian::EdgeWeights::CotanTimesLength, "edge weight is equal to edge length times cotangent weight" ); -MR_ADD_PYTHON_FUNCTION( mrmeshpy, findMaxMeshDistanceSqOneWay, &findMaxDistanceSqOneWay, "returns the maximum of the distances from each B-mesh point to A-mesh" ) -MR_ADD_PYTHON_FUNCTION( mrmeshpy, findMaxMeshDistanceSq, &findMaxDistanceSq, "returns the maximum of the distances from each mesh point to another mesh in both directions" ) - -MR_ADD_PYTHON_FUNCTION( mrmeshpy, findDegenerateFaces, &findDegenerateFaces, "finds faces which aspect ratio >= criticalAspectRatio" ) + m.def( "positionVertsSmoothly", &MR::positionVertsSmoothly, + pybind11::arg( "mesh" ), pybind11::arg( "verts" ), pybind11::arg( "egdeWeightsType" ) = MR::Laplacian::EdgeWeights::Cotan, + "Puts given vertices in such positions to make smooth surface both inside verts-region and on its boundary" ); +} ) diff --git a/test_python/test_boolean.py b/test_python/test_boolean.py index f4881c8fe324..b8e2cddd1a17 100644 --- a/test_python/test_boolean.py +++ b/test_python/test_boolean.py @@ -4,7 +4,7 @@ def test_boolean(): torusIntersected = mrmesh.makeTorusWithSelfIntersections(2, 1, 10, 10, None) - mrmesh.fix_self_intersections(torusIntersected, 0.1) + mrmesh.fixSelfIntersections(torusIntersected, 0.1) torus = mrmesh.makeTorus(2, 1, 10, 10, None) @@ -18,22 +18,18 @@ def test_boolean(): torus2 = mrmesh.makeTorus(2, 1, 10, 10, None) torus2.transform(diffXf) - torus1 = torus - p = torus1.points.vec.size() - mrmesh.boolean_sub(torus1, torus2, 0.05) - p_sub = torus1.points.vec.size() + p = torus.points.vec.size() - torus1 = torus - mrmesh.boolean_union(torus1, torus2, 0.05) - p_union = torus1.points.vec.size() + torusS = mrmesh.voxelBooleanSubtract(torus, torus2, 0.05) + p_sub = torusS.points.vec.size() - torus1 = torus - mrmesh.boolean_intersect(torus1, torus2, 0.05) - p_intersect = torus1.points.vec.size() + torusU = mrmesh.voxelBooleanUnite(torus, torus2, 0.05) + p_union = torusU.points.vec.size() - assert( p == 100) + torusI = mrmesh.voxelBooleanIntersect(torus, torus2, 0.05) + p_intersect = torusI.points.vec.size() - import math - assert( math.isclose( p_sub, p_intersect, rel_tol=1e-2) ) - assert( math.isclose( p_sub / p, 400, abs_tol=100) ) - assert( math.isclose( p_sub / p_union, 0.7, rel_tol=0.2) ) + assert( p == 100) + assert( p_sub == 43132) + assert( p_union == 63114) + assert( p_intersect == 23006) diff --git a/test_python/test_collision.py b/test_python/test_collision.py index 2a6b02155ddb..7197740308c2 100644 --- a/test_python/test_collision.py +++ b/test_python/test_collision.py @@ -2,7 +2,7 @@ import pytest -def test_col(): +def test_collision(): torus = mrmesh.makeTorus(2, 1, 10, 10, None) torus2 = mrmesh.makeTorus(2, 1, 10, 10, None) @@ -15,7 +15,7 @@ def test_col(): xf = mrmesh.AffineXf3f() torus1 = torus - pairs = mrmesh.find_colliding_faces(torus1, torus2, None, False) + pairs = mrmesh.findCollidingTriangles(mrmesh.MeshPart(torus1), mrmesh.MeshPart(torus2)) # at least 100 triangles should collide for that transforms assert (len(pairs) > 103) diff --git a/test_python/test_deleteFaces.py b/test_python/test_deleteFaces.py index 3649a04b458b..bcbbf0d15c61 100644 --- a/test_python/test_deleteFaces.py +++ b/test_python/test_deleteFaces.py @@ -11,7 +11,7 @@ def test_delete_faces(): faceBitSetToDelete.resize(5, False) faceBitSetToDelete.set(mrmesh.FaceId(1), True) oldFaceBS = torus.topology.getValidFaces() - mrmesh.delete_faces(torus.topology, faceBitSetToDelete) + torus.topology.deleteFaces(faceBitSetToDelete) deletedBitSet = oldFaceBS - torus.topology.getValidFaces() diff --git a/test_python/test_distanceMap.py b/test_python/test_distanceMap.py index 3589d4a04f0f..24e1b0fdbcb8 100644 --- a/test_python/test_distanceMap.py +++ b/test_python/test_distanceMap.py @@ -22,7 +22,7 @@ def test_distanceMap(): params.orgPoint.y = -(R1+R2) params.orgPoint.z = -R2 - map = mrmesh.compute_distance_map(torus, params) + map = mrmesh.computeDistanceMapD(mrmesh.MeshPart(torus), params) #mrmesh.save_mesh(torus, "c:/temp/torus.mrmesh") assert (map.isValid(0,0) == False) diff --git a/test_python/test_fillHole.py b/test_python/test_fillHole.py index e8ca0515b144..ba41c1c85b45 100644 --- a/test_python/test_fillHole.py +++ b/test_python/test_fillHole.py @@ -8,7 +8,7 @@ def test_fillHole(): faceBitSetToDelete.resize(5, False) faceBitSetToDelete.set(mrmesh.FaceId(1), True) - mrmesh.delete_faces(torus.topology, faceBitSetToDelete) + torus.topology.deleteFaces(faceBitSetToDelete) holes = torus.topology.findHoleRepresentiveEdges() diff --git a/test_python/test_findMaxDistanceSqOneWay.py b/test_python/test_findMaxDistanceSqOneWay.py index d60a0f3dd56d..65c1f4069eae 100644 --- a/test_python/test_findMaxDistanceSqOneWay.py +++ b/test_python/test_findMaxDistanceSqOneWay.py @@ -12,6 +12,6 @@ def test_findMaxDistanceSqOneWay(): diffXf = mrmesh.AffineXf3f.translation(transVector) torus2.transform(diffXf) - distSq = mrmesh.findMaxMeshDistanceSqOneWay(mrmesh.MeshPart(torus), mrmesh.MeshPart(torus2), diffXf.inverse(), 1e35) + distSq = mrmesh.findMaxDistanceSqOneWay(mrmesh.MeshPart(torus), mrmesh.MeshPart(torus2), diffXf.inverse(), 1e35) assert (distSq < 69.28205**2) \ No newline at end of file diff --git a/test_python/test_findMaxMeshDistanceSq.py b/test_python/test_findMaxMeshDistanceSq.py index 584a04a7277f..a8489bda7799 100644 --- a/test_python/test_findMaxMeshDistanceSq.py +++ b/test_python/test_findMaxMeshDistanceSq.py @@ -12,6 +12,6 @@ def test_findMaxMeshDistanceSq(): diffXf = mrmesh.AffineXf3f.translation(transVector) torus2.transform(diffXf) - distSq = mrmesh.findMaxMeshDistanceSq(mrmesh.MeshPart(torus), mrmesh.MeshPart(torus2), diffXf.inverse(), 1e35) + distSq = mrmesh.findMaxDistanceSq(mrmesh.MeshPart(torus), mrmesh.MeshPart(torus2), diffXf.inverse(), 1e35) assert (distSq < 69.28205**2) \ No newline at end of file diff --git a/test_python/test_fixSpikes.py b/test_python/test_fixSpikes.py index 3a55570a35d7..9b3475f913fb 100644 --- a/test_python/test_fixSpikes.py +++ b/test_python/test_fixSpikes.py @@ -1,14 +1,14 @@ from helper import * import pytest -def test_fixSpikec(): +def test_fixSpikes(): R1 = 2 R2_1 = 1 R2_2 = 2.5 torus = mrmesh.makeTorusWithSpikes(R1, R2_1, R2_2, 10, 10, None) # (5 =~ 3pi/2) rad - minSumAngle - mrmesh.remove_spikes(torus, 3, 5, None) + mrmesh.removeSpikes(torus, 3, 5, None) # now all points are in that range from the center # comment 'remove spikes' to catch this assert diff --git a/test_python/test_fixTunnels.py b/test_python/test_fixTunnels.py index b06b31f9b479..abcfaf8cf05f 100644 --- a/test_python/test_fixTunnels.py +++ b/test_python/test_fixTunnels.py @@ -4,7 +4,7 @@ def test_fixTunnels(): torus = mrmesh.makeTorus(2, 1, 10, 10, None) - tunnelFaces = mrmesh.get_tunnel_faces(mrmesh.MeshPart(torus), 100500) + tunnelFaces = mrmesh.detectTunnelFaces(mrmesh.MeshPart(torus), 100500) # one circle with 2-faces width assert (tunnelFaces.count() == 20) diff --git a/test_python/test_fixUndercuts.py b/test_python/test_fixUndercuts.py index a6e4971dc0b5..f6715adf4ed0 100644 --- a/test_python/test_fixUndercuts.py +++ b/test_python/test_fixUndercuts.py @@ -9,6 +9,6 @@ def test_fixUndercuts(): dir.y = 0 dir.z = 1 - mrmesh.fix_undercuts(torus, dir, 0.2, 0.) + mrmesh.fixUndercuts(torus, dir, 0.2, 0.) assert(torus.points.vec.size() > 2900) diff --git a/test_python/test_make_cube.py b/test_python/test_make_cube.py index e9b61335008f..d759254ba1d3 100644 --- a/test_python/test_make_cube.py +++ b/test_python/test_make_cube.py @@ -18,7 +18,7 @@ def test_makeCube(): xf = mrmesh.AffineXf3f() cube1 = cube - pairs = mrmesh.find_colliding_faces(cube1, cube2, None, False) + pairs = mrmesh.findCollidingTriangles(mrmesh.MeshPart(cube1), mrmesh.MeshPart(cube2), None, False) #at least 100 triangles should collide for that transforms assert (len(pairs) < 23) diff --git a/test_python/test_positionVertsSmooth.py b/test_python/test_positionVertsSmooth.py index b0a5e6be0671..ea916c073a0b 100644 --- a/test_python/test_positionVertsSmooth.py +++ b/test_python/test_positionVertsSmooth.py @@ -11,7 +11,7 @@ def test_positionVersSmooth(): #params = mrmesh.LaplacianEdgeWeightsParam.Unit params = mrmesh.LaplacianEdgeWeightsParam.Cotan verts = torus.topology.getValidVerts() - mrmesh.position_verts_smoothly(torus, verts, params) + mrmesh.positionVertsSmoothly(torus, verts, params) # now all points are in that range from the center for i in torus.points.vec: diff --git a/test_python/test_signedDistance.py b/test_python/test_signedDistance.py index 93935a4c836e..be581f9afa74 100644 --- a/test_python/test_signedDistance.py +++ b/test_python/test_signedDistance.py @@ -9,8 +9,8 @@ def test_signedDistance(): xf = mrmesh.AffineXf3f() - res = mrmesh.find_signed_distance(torus, torus2, xf) - resRevert = mrmesh.find_signed_distance(torus2, torus, xf) + res = mrmesh.findSignedDistance(mrmesh.MeshPart(torus), mrmesh.MeshPart(torus2), xf) + resRevert = mrmesh.findSignedDistance(mrmesh.MeshPart(torus2),mrmesh.MeshPart( torus), xf) # probably, we need negative comparison assert (res.signedDist == resRevert.signedDist) diff --git a/test_python/test_stitchTwoHoles.py b/test_python/test_stitchTwoHoles.py index 643642f50d3b..b9532540b86e 100644 --- a/test_python/test_stitchTwoHoles.py +++ b/test_python/test_stitchTwoHoles.py @@ -10,12 +10,12 @@ def test_stitch_TwoHoles(): assert(len(holes)==2) params = mrmesh.StitchHolesParams() - mrmesh.set_stitch_holes_metric_edge_length(params, torus) - mrmesh.stitch_holes(torus, holes[0], holes[1], params) + params.metric = mrmesh.getEdgeLengthStitchMetric(torus) + mrmesh.buildCylinderBetweenTwoHoles(torus, holes[0], holes[1], params) holes = torus.topology.findHoleRepresentiveEdges() assert(len(holes)==0) - mrmesh.stitch_two_holes(torus2, params) + mrmesh.buildCylinderBetweenTwoHoles(torus2, params) holes = torus2.topology.findHoleRepresentiveEdges() assert(len(holes)==0) \ No newline at end of file diff --git a/test_python/test_subdivider.py b/test_python/test_subdivider.py index 44497b8404f3..50579a0b14e2 100644 --- a/test_python/test_subdivider.py +++ b/test_python/test_subdivider.py @@ -11,6 +11,6 @@ def test_subdivider(): settings.maxEdgeLen = 0.3 settings.maxEdgeSplits = 5 settings.maxDeviationAfterFlip = 0.2 - mrmesh.subdivide_mesh(torus, settings) + mrmesh.subdivideMesh(torus, settings) assert (torus.topology.getValidFaces().count() > countInit)