Skip to content

Commit

Permalink
Bind expected to python and docs update (#541)
Browse files Browse the repository at this point in the history
* Bind expected to python and docs update

* more docs update
  • Loading branch information
Grantim authored Sep 23, 2022
1 parent b543faf commit 5539440
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 150 deletions.
8 changes: 5 additions & 3 deletions doxygen/EmbendedPython.dox
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace MR {
/** \page EmbendedPythonOverview Python overview

Python is one of the most popular and widely used programming language in scientific researches, machine learning, data analysis and advanced testing.\n
\b Requires \b python \b 3.10 (3.9 on linux)
\b Requires \b python \b 3.10 (3.8 on Ubuntu 20.04)

MeshLib library allows you to call python scripts directly from the command line or use particular functions by `import mrmeshpy`.

Expand All @@ -20,11 +20,13 @@ MeshLib provides several usage options\n
\code
import mrmeshpy

mesh = mrmeshpy.load_mesh("mesh.stl")
expMesh = mrmeshpy.loadMesh(mrmeshpy.Path("mesh.stl"))
assert(expMesh.has_value())
mesh = expMesh.value()
# ...
# some mesh modifications can be perform here
# ...
mrmeshpy.save_mesh("mesh.ply")
mrmeshpy.saveMesh(mesh, mrmeshpy.Path("mesh.ply"))
\endcode
2. Run scritp with MeshInspector executable file (MeshInspector.exe on windows or ./MRApp on linux)
\code
Expand Down
15 changes: 9 additions & 6 deletions doxygen/HowToExamples.dox
Original file line number Diff line number Diff line change
Expand Up @@ -135,26 +135,29 @@ In this section we provide the same examples but with python code\n
Load and save example:
\code
import mrmeshpy
mesh = mrmeshpy.load_mesh("mesh.stl")
mrmeshpy.save_mesh("mesh.ply")
expectedMesh = mrmeshpy.loadMesh(mrmeshpy.Path("mesh.stl"))
if expectedMesh.has_value():
mrmeshpy.saveMesh(expectedMesh.value(),mrmeshpy.Path("mesh.ply"))
\endcode

Mesh modification examples
\code
import mrmeshpy
mesh = mrmeshpy.load_mesh("mesh.stl")
expectedMesh = mrmeshpy.loadMesh(mrmeshpy.Path("mesh.stl"))
assert(expectedMesh.has_value())
mesh = expectedMesh.value()

relaxParams = mrmeshpy.MeshRelaxParams()
relaxParams.iterations = 5
mrmeshpy.relax(mesh, relaxParams)

props = mrmeshpy.SubdivideSettings()
props.maxDeviationAfterFlip = 0.5
mrmeshpy.subdivide_mesh(mesh,props)
mrmeshpy.subdivideMesh(mesh,props)

plusZ = mrmeshpy.Vector3()
plusZ = mrmeshpy.Vector3f()
plusZ.z = 1.0
rotationXf = mrmeshpy.AffineXf3.linear( mrmeshpy.Matrix3.rotation( plusZ, 3.1415*0.5 ) )
rotationXf = mrmeshpy.AffineXf3f.linear( mrmeshpy.Matrix3f.rotation( plusZ, 3.1415*0.5 ) )
mesh.transform(rotationXf)
\endcode

Expand Down
4 changes: 2 additions & 2 deletions python_scripts/test_script.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
print('Import mrmesh')
import meshlib.mrmeshpy as mrmesh
import mrmeshpy as mrmesh

print('Start test script')

mesh = mrmesh.loadMesh("M:\\sectioning\\sectioning\\468\\Final Output\\P523468_101_OtherFiles\\stitched.mrmesh")
mesh = mrmesh.loadMesh(mrmesh.Path("M:\\sectioning\\sectioning\\468\\Final Output\\P523468_101_OtherFiles\\stitched.mrmesh")).value()
holes = mesh.topology.findHoleRepresentiveEdges()
vec3 = mrmesh.Vector3f();
vec3.z = 1.0;
Expand Down
11 changes: 11 additions & 0 deletions source/MRMesh/MRPython.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <pybind11/operators.h>
#include <pybind11/stl_bind.h>
#include <pybind11/numpy.h>
#include <tl/expected.hpp>
#include <functional>
#include <filesystem>
#include <unordered_map>
Expand Down Expand Up @@ -45,6 +46,16 @@ MR_ADD_PYTHON_CUSTOM_DEF( moduleName, name, [] (pybind11::module_& m)\
def( "clear", &vecType::clear ); \
} )

#define MR_ADD_PYTHON_EXPECTED( moduleName, name, type, errorType )\
MR_ADD_PYTHON_CUSTOM_DEF( moduleName, name, [] (pybind11::module_& m)\
{\
using expectedType = tl::expected<type,errorType>;\
pybind11::class_<expectedType>(m, #name ).\
def( "has_value", &expectedType::has_value ).\
def( "value", ( type& ( expectedType::* )( )& )& expectedType::value, pybind11::return_value_policy::reference ).\
def( "error", ( const errorType& ( expectedType::* )( )const& )& expectedType::error );\
} )

enum StreamType
{
Stdout,
Expand Down
31 changes: 31 additions & 0 deletions source/mrmeshpy/MRPythonBaseExposing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,25 @@
#include "MRMesh/MREdgePaths.h"
#include "MRMesh/MRFillContour.h"
#include "MRMesh/MRExpandShrink.h"
#include "MRMesh/MRColor.h"
#include <tl/expected.hpp>

MR_INIT_PYTHON_MODULE( mrmeshpy )

MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, ExpectedVoid, []( pybind11::module_& m )\
{
using expectedType = tl::expected<void, std::string>;
pybind11::class_<expectedType>( m, "ExpectedVoid" ).
def( "has_value", &expectedType::has_value ).
def( "error", ( const std::string& ( expectedType::* )( )const& )& expectedType::error );
} )

MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, Path, [] ( pybind11::module_& m )
{
pybind11::class_<std::filesystem::path>( m, "Path" ).
def( pybind11::init<const std::string&>() );
} )

MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, Box3f, [] ( pybind11::module_& m )
{
pybind11::class_<MR::Box3f>( m, "Box3f", "Box given by its min- and max- corners" ).
Expand Down Expand Up @@ -71,6 +87,21 @@ MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, Vector2f, [] ( pybind11::module_& m )
def( "normalized", &MR::Vector2f::normalized );
} )

MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, Color, [] ( pybind11::module_& m )
{
pybind11::class_<MR::Color>( m, "Color" ).
def( pybind11::init<>() ).
def( pybind11::init<int, int, int, int>(),
pybind11::arg( "r" ), pybind11::arg( "g" ), pybind11::arg( "b" ), pybind11::arg( "a" ) = 255 ).
def( pybind11::init<float, float, float, float>(),
pybind11::arg( "r" ), pybind11::arg( "g" ), pybind11::arg( "b" ), pybind11::arg( "a" ) = 1.0f ).
def_readwrite( "r", &MR::Color::r ).
def_readwrite( "r", &MR::Color::g ).
def_readwrite( "r", &MR::Color::b ).
def_readwrite( "r", &MR::Color::a );
} )
MR_ADD_PYTHON_VEC( mrmeshpy, vectorColor, MR::Color )

MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, Vector3f, [] ( pybind11::module_& m )
{
pybind11::class_<MR::Vector3f>( m, "Vector3f", "three-dimensional vector" ).
Expand Down
156 changes: 45 additions & 111 deletions source/mrmeshpy/MRPythonIO.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "MRMesh/MRPython.h"
#include <pybind11/iostream.h>
#include <pybind11/functional.h>
#include "MRMesh/MRObjectsAccess.h"
#include "MRMesh/MRSceneRoot.h"
#include "MRMesh/MRObjectMesh.h"
Expand All @@ -20,20 +21,6 @@

using namespace MR;

bool pythonSaveMeshToAnyFormat( const Mesh& mesh, const std::string& path )
{
auto res = MR::MeshSave::toAnySupportedFormat( mesh, path );
return res.has_value();
}

Mesh pythonLoadMeshFromAnyFormat( const std::string& path )
{
auto res = MR::MeshLoad::fromAnySupportedFormat( path );
if ( res.has_value() )
return std::move( *res );
return {};
}

namespace MR
{

Expand Down Expand Up @@ -120,160 +107,107 @@ class PythonIstreamBuf : public std::streambuf

}

Mesh pythonLoadMeshFromAnyFormat( pybind11::object fileHandle, const std::string& extension )
tl::expected<MR::Mesh, std::string> pythonLoadMeshFromAnyFormat( pybind11::object fileHandle, const std::string& extension )
{
if ( !( pybind11::hasattr( fileHandle, "read" ) && pybind11::hasattr( fileHandle, "seek" ) && pybind11::hasattr( fileHandle, "tell" ) ) )
{
spdlog::error( "Argument is not file handle" );
return {};
}
return tl::make_unexpected( "Argument is not file handle" );
PythonIstreamBuf streambuf( fileHandle );
std::istream ifs( &streambuf );
auto res = MR::MeshLoad::fromAnySupportedFormat( ifs, extension );
if ( res.has_value() )
return std::move( *res );
std::cout << res.error() << '\n';
return {};
return MR::MeshLoad::fromAnySupportedFormat( ifs, extension );
}

bool pythonSaveMeshToAnyFormat( const Mesh& mesh, const std::string& extension, pybind11::object fileHandle )
tl::expected<void, std::string> pythonSaveMeshToAnyFormat( const Mesh& mesh, const std::string& extension, pybind11::object fileHandle )
{
if ( !( pybind11::hasattr( fileHandle, "write" ) && pybind11::hasattr( fileHandle, "flush" ) ) )
{
spdlog::error( "Argument is not file handle" );
return false;
}
return tl::make_unexpected( "Argument is not file handle" );
pybind11::detail::pythonbuf pybuf( fileHandle );
std::ostream outfs( &pybuf );
auto res = MR::MeshSave::toAnySupportedFormat( mesh, outfs, extension );
return res.has_value();
return MR::MeshSave::toAnySupportedFormat( mesh, outfs, extension );
}

bool pythonSaveLinesToAnyFormat( const MR::Polyline3& lines, const std::string& path )
{
auto res = MR::LinesSave::toAnySupportedFormat( lines, path );
return res.has_value();
}

bool pythonSaveLinesToAnyFormat( const MR::Polyline3& lines, const std::string& extension, pybind11::object fileHandle )
tl::expected<void, std::string> pythonSaveLinesToAnyFormat( const MR::Polyline3& lines, const std::string& extension, pybind11::object fileHandle )
{
if ( !( pybind11::hasattr( fileHandle, "write" ) && pybind11::hasattr( fileHandle, "flush" ) ) )
{
spdlog::error( "Argument is not file handle" );
return false;
}
return tl::make_unexpected( "Argument is not file handle" );
pybind11::detail::pythonbuf pybuf( fileHandle );
std::ostream outfs( &pybuf );
auto res = MR::LinesSave::toAnySupportedFormat( lines, outfs, extension );
return res.has_value();
return MR::LinesSave::toAnySupportedFormat( lines, outfs, extension );
}

MR::Polyline3 pythonLoadLinesFromAnyFormat( const std::string& path )
{
auto res = MR::LinesLoad::fromAnySupportedFormat( path );
if ( res.has_value() )
return std::move( *res );
return {};
}

MR::Polyline3 pythonLoadLinesFromAnyFormat( pybind11::object fileHandle, const std::string& extension )
tl::expected<Polyline3, std::string> pythonLoadLinesFromAnyFormat( pybind11::object fileHandle, const std::string& extension )
{
if ( !( pybind11::hasattr( fileHandle, "read" ) && pybind11::hasattr( fileHandle, "seek" ) && pybind11::hasattr( fileHandle, "tell" ) ) )
{
spdlog::error( "Argument is not file handle" );
return {};
}
return tl::make_unexpected( "Argument is not file handle" );
PythonIstreamBuf streambuf( fileHandle );
std::istream ifs( &streambuf );
auto res = MR::LinesLoad::fromAnySupportedFormat( ifs, extension );
if ( res.has_value() )
return std::move( *res );
std::cout << res.error() << '\n';
return {};
}

bool pythonSavePointCloudToAnyFormat( const PointCloud& points, const std::string& path )
{
auto res = MR::PointsSave::toAnySupportedFormat( points, path );
return res.has_value();
return MR::LinesLoad::fromAnySupportedFormat( ifs, extension );
}

bool pythonSavePointCloudToAnyFormat( const PointCloud& points, const std::string& extension, pybind11::object fileHandle )
tl::expected<void, std::string> pythonSavePointCloudToAnyFormat( const PointCloud& points, const std::string& extension, pybind11::object fileHandle )
{
if ( !( pybind11::hasattr( fileHandle, "write" ) && pybind11::hasattr( fileHandle, "flush" ) ) )
{
spdlog::error( "Argument is not file handle" );
return false;
}
return tl::make_unexpected( "Argument is not file handle" );
pybind11::detail::pythonbuf pybuf( fileHandle );
std::ostream outfs( &pybuf );
auto res = MR::PointsSave::toAnySupportedFormat( points, outfs, extension );
return res.has_value();
return MR::PointsSave::toAnySupportedFormat( points, outfs, extension );
}

PointCloud pythonLoadPointCloudFromAnyFormat( const std::string& path )
{
auto res = MR::PointsLoad::fromAnySupportedFormat( path );
if ( res.has_value() )
return std::move( *res );
return {};
}

PointCloud pythonLoadPointCloudFromAnyFormat( pybind11::object fileHandle, const std::string& extension )
tl::expected<PointCloud, std::string> pythonLoadPointCloudFromAnyFormat( pybind11::object fileHandle, const std::string& extension )
{
if ( !( pybind11::hasattr( fileHandle, "read" ) && pybind11::hasattr( fileHandle, "seek" ) && pybind11::hasattr( fileHandle, "tell" ) ) )
{
spdlog::error( "Argument is not file handle" );
return {};
}
return tl::make_unexpected( "Argument is not file handle" );
PythonIstreamBuf streambuf( fileHandle );
std::istream ifs( &streambuf );
auto res = MR::PointsLoad::fromAnySupportedFormat( ifs, extension );
if ( res.has_value() )
return std::move( *res );
std::cout << res.error() << '\n';
return {};
return MR::PointsLoad::fromAnySupportedFormat( ifs, extension );
}

MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, SaveMesh, [] ( pybind11::module_& m )
{
m.def( "saveMesh", ( bool( * )( const MR::Mesh&, const std::string& ) )& pythonSaveMeshToAnyFormat,
pybind11::arg( "mesh" ), pybind11::arg( "path" ), "saves mesh in file of known format/extension" );
m.def( "saveMesh", ( bool( * )( const MR::Mesh&, const std::string&, pybind11::object ) )& pythonSaveMeshToAnyFormat,
m.def( "saveMesh",
( tl::expected<void, std::string>( * )( const MR::Mesh&, const std::filesystem::path&, const Vector<Color, VertId>*, ProgressCallback ) )& MR::MeshSave::toAnySupportedFormat,
pybind11::arg( "mesh" ), pybind11::arg( "path" ), pybind11::arg( "colors" ) = nullptr, pybind11::arg( "callback" ) = ProgressCallback{},
"detects the format from file extension and save mesh to it" );
m.def( "saveMesh", ( tl::expected<void, std::string>( * )( const MR::Mesh&, const std::string&, pybind11::object ) )& pythonSaveMeshToAnyFormat,
pybind11::arg( "mesh" ), pybind11::arg( "extension" ), pybind11::arg( "fileHandle" ), "saves mesh in python file handler, second arg: extension (`*.ext` format)" );
} )
MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, LoadMesh, [] ( pybind11::module_& m )
{
m.def( "loadMesh", ( MR::Mesh( * )( const std::string& ) )& pythonLoadMeshFromAnyFormat,
pybind11::arg( "path" ), "load mesh of known format" );
m.def( "loadMesh", ( MR::Mesh( * )( pybind11::object, const std::string& ) )& pythonLoadMeshFromAnyFormat,
m.def( "loadMesh",
( tl::expected<MR::Mesh, std::string>( * )( const std::filesystem::path&, Vector<Color, VertId>*, ProgressCallback ) )& MR::MeshLoad::fromAnySupportedFormat,
pybind11::arg( "path" ), pybind11::arg( "colors" ) = nullptr, pybind11::arg( "callback" ) = ProgressCallback{},
"detects the format from file extension and loads mesh from it" );
m.def( "loadMesh", ( tl::expected<MR::Mesh, std::string>( * )( pybind11::object, const std::string& ) )& pythonLoadMeshFromAnyFormat,
pybind11::arg( "fileHandle" ), pybind11::arg( "extension" ), "load mesh from python file handler, second arg: extension (`*.ext` format)" );
} )
MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, SaveLines, [] ( pybind11::module_& m )
{
m.def( "saveLines", ( bool( * )( const MR::Polyline3&, const std::string& ) )& pythonSaveLinesToAnyFormat,
pybind11::arg( "polyline" ), pybind11::arg( "path" ), "saves lines in file of known format/extension" );
m.def( "saveLines", ( bool( * )( const MR::Polyline3&, const std::string&, pybind11::object ) )& pythonSaveLinesToAnyFormat,
m.def( "saveLines", ( tl::expected<void, std::string>( * )( const MR::Polyline3&, const std::filesystem::path&, ProgressCallback ) )& MR::LinesSave::toAnySupportedFormat,
pybind11::arg( "polyline" ), pybind11::arg( "path" ), pybind11::arg( "callback" ) = ProgressCallback{},
"detects the format from file extension and saves polyline in it" );
m.def( "saveLines", ( tl::expected<void, std::string>( * )( const MR::Polyline3&, const std::string&, pybind11::object ) )& pythonSaveLinesToAnyFormat,
pybind11::arg( "polyline" ), pybind11::arg( "extension" ), pybind11::arg( "fileHandle" ), "saves lines in python file handler, second arg: extension (`*.ext` format)" );
} )
MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, LoadLines, [] ( pybind11::module_& m )
{
m.def( "loadLines", ( MR::Polyline3( * )( const std::string& ) )& pythonLoadLinesFromAnyFormat,
pybind11::arg( "path" ), "load lines of known format" );
m.def( "loadLines", ( MR::Polyline3( * )( pybind11::object, const std::string& ) )& pythonLoadLinesFromAnyFormat,
m.def( "loadLines", ( tl::expected<Polyline3, std::string>( * )( const std::filesystem::path&, ProgressCallback ) )& MR::LinesLoad::fromAnySupportedFormat,
pybind11::arg( "path" ), pybind11::arg( "callback" ) = ProgressCallback{},
"detects the format from file extension and loads polyline from it" );
m.def( "loadLines", ( tl::expected<Polyline3, std::string>( * )( pybind11::object, const std::string& ) )& pythonLoadLinesFromAnyFormat,
pybind11::arg( "fileHandle" ), pybind11::arg( "extension" ), "load lines from python file handler, second arg: extension (`*.ext` format)" );
} )
MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, SavePoints, [] ( pybind11::module_& m )
{
m.def( "savePoints", ( bool( * )( const MR::PointCloud&, const std::string& ) )& pythonSavePointCloudToAnyFormat,
pybind11::arg( "pointCloud" ), pybind11::arg( "path" ), "saves point cloud in file of known format/extension" );
m.def( "savePoints", ( bool( * )( const MR::PointCloud&, const std::string&, pybind11::object ) )& pythonSavePointCloudToAnyFormat,
m.def( "savePoints", ( tl::expected<void, std::string>( * )( const MR::PointCloud&, const std::filesystem::path&, const Vector<Color, VertId>*, ProgressCallback ) )& MR::PointsSave::toAnySupportedFormat,
pybind11::arg( "pointCloud" ), pybind11::arg( "path" ), pybind11::arg( "colors" ) = nullptr, pybind11::arg( "callback" ) = ProgressCallback{},
"detects the format from file extension and save points to it" );
m.def( "savePoints", ( tl::expected<void, std::string>( * )( const MR::PointCloud&, const std::string&, pybind11::object ) )& pythonSavePointCloudToAnyFormat,
pybind11::arg( "pointCloud" ), pybind11::arg( "extension" ), pybind11::arg( "fileHandle" ), "saves point cloud in python file handler, second arg: extension (`*.ext` format)" );
} )
MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, LoadPoints, [] ( pybind11::module_& m )
{
m.def( "loadPoints", ( MR::PointCloud( * )( const std::string& ) )& pythonLoadPointCloudFromAnyFormat,
pybind11::arg( "path" ), "load point cloud of known format" );
m.def( "loadPoints", ( MR::PointCloud( * )( pybind11::object, const std::string& ) )& pythonLoadPointCloudFromAnyFormat,
m.def( "loadPoints", ( tl::expected<PointCloud, std::string>( * )( const std::filesystem::path&, Vector<Color, VertId>*, ProgressCallback ) )& MR::PointsLoad::fromAnySupportedFormat,
pybind11::arg( "path" ), pybind11::arg( "colors" ) = nullptr, pybind11::arg( "callback" ) = ProgressCallback{},
"detects the format from file extension and loads points from it" );
m.def( "loadPoints", ( tl::expected<PointCloud, std::string>( * )( pybind11::object, const std::string& ) )& pythonLoadPointCloudFromAnyFormat,
pybind11::arg( "fileHandle" ), pybind11::arg( "extension" ), "load point cloud from python file handler, second arg: extension (`*.ext` format)" );
} )
6 changes: 6 additions & 0 deletions source/mrmeshpy/MRPythonMeshExposing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, Vector, [] ( pybind11::module_& m )
pybind11::class_<MR::Vector<Vector2f, VertId>>( m, "VertCoords2" ).
def( pybind11::init<>() ).
def_readwrite( "vec", &MR::Vector<Vector2f, VertId>::vec_ );

pybind11::class_<MR::Vector<Color, VertId>>( m, "VertColorMap" ).
def( pybind11::init<>() ).
def_readwrite( "vec", &MR::Vector<Color, VertId>::vec_ );
} )

MR::MeshTopology topologyFromTriangles( const Triangulation& t, const MeshBuilder::BuildSettings& s )
Expand Down Expand Up @@ -168,6 +172,8 @@ MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, Mesh, [] ( pybind11::module_& m )
m.def( "copyMesh", &pythonCopyMeshFunction, pybind11::arg( "mesh" ), "returns copy of input mesh" );
} )

MR_ADD_PYTHON_EXPECTED( mrmeshpy, ExpectedMesh, MR::Mesh, std::string )

MR_ADD_PYTHON_CUSTOM_DEF( mrmeshpy, MeshPart, [] ( pybind11::module_& m )
{
pybind11::class_<MR::MeshPart>( m, "MeshPart", "stores reference on whole mesh (if region is nullptr) or on its part (if region pointer is valid)" ).
Expand Down
Loading

0 comments on commit 5539440

Please sign in to comment.