From 45b648e11ee3876e5cb71e82561a70b1ce9b1817 Mon Sep 17 00:00:00 2001 From: openclaw-runner Date: Mon, 24 Aug 2026 15:11:41 +0200 Subject: [PATCH] Add docs page for switching from other geometry libraries Developers evaluating MeshLib usually arrive with an existing CGAL, libigl, Open3D, PCL or VTK pipeline and their first question is how their current calls map onto MeshLib. The docs had no single place answering that, so every such question was handled ad hoc. The page pairs the most frequently ported operations - boolean, decimate, signed distance, ICP, surface reconstruction, sampling, normals, mesh IO - with the equivalent MeshLib code, plus an in-memory numpy bridge for VTK users in Python. Placed as a top-level menu item between Examples and Package Overview so it is reachable without knowing our terminology. --- .../SwitchingFromOtherLibraries.dox | 347 ++++++++++++++++++ doxygen/layout_templates/base_struct.xml | 1 + 2 files changed, 348 insertions(+) create mode 100644 doxygen/general_pages/SwitchingFromOtherLibraries.dox diff --git a/doxygen/general_pages/SwitchingFromOtherLibraries.dox b/doxygen/general_pages/SwitchingFromOtherLibraries.dox new file mode 100644 index 000000000000..a783e4bfcd35 --- /dev/null +++ b/doxygen/general_pages/SwitchingFromOtherLibraries.dox @@ -0,0 +1,347 @@ +/** + +\page SwitchingFromOtherLibraries Switching from Other Libraries to MeshLib + +\tableofcontents + +MeshLib is a C++/Python geometry library focused on robust boolean operations, offsets, voxel-based reconstruction, decimation and remeshing. +If you already work with CGAL, libigl, Open3D, PCL or VTK, the pairs below show the equivalent MeshLib code for the operations users most often port over. +Each snippet is minimal and self-contained — copy, adapt, compile. + +We do not benchmark on this page. For measured MeshLib vs VTK comparisons (decimation, boolean, subdivision), see [the benchmark article](https://meshlib.io/blog/vtk-alternative-comparison/). + +Every C++ snippet has a one-to-one equivalent in our Python bindings. + +# CGAL + +## Boolean union of two triangle meshes + +CGAL: +\code{.cpp} +#include +#include +#include +#include +namespace PMP = CGAL::Polygon_mesh_processing; +using Mesh = CGAL::Surface_mesh; + +Mesh a, b, out; +PMP::IO::read_polygon_mesh("a.off", a); +PMP::IO::read_polygon_mesh("b.off", b); +PMP::corefine_and_compute_union(a, b, out); +CGAL::IO::write_polygon_mesh("out.off", out); +\endcode + +MeshLib: +\code{.cpp} +#include +#include +#include + +auto a = MR::MeshLoad::fromAnySupportedFormat("a.off").value(); +auto b = MR::MeshLoad::fromAnySupportedFormat("b.off").value(); +auto res = MR::boolean(a, b, MR::BooleanOperation::Union); +MR::MeshSave::toAnySupportedFormat(res.mesh, "out.off"); +\endcode + +## Connected components of a mesh + +CGAL: +\code{.cpp} +#include +namespace PMP = CGAL::Polygon_mesh_processing; +using face_descriptor = boost::graph_traits::face_descriptor; + +auto fccmap = mesh.add_property_map("f:CC").first; +std::size_t n = PMP::connected_components(mesh, fccmap); +// fccmap[f] = component id +\endcode + +MeshLib: +\code{.cpp} +#include + +auto [labels, n] = MR::MeshComponents::getAllComponentsMap(mesh); +// labels[face] = component id +\endcode + +# libigl + +## Decimate a mesh to half its faces + +libigl: +\code{.cpp} +#include +#include + +Eigen::MatrixXd V, U; +Eigen::MatrixXi F, G; +Eigen::VectorXi J, I; +igl::read_triangle_mesh("in.obj", V, F); +igl::decimate(V, F, F.rows() / 2, U, G, J, I); +\endcode + +MeshLib: +\code{.cpp} +#include +#include +#include + +auto mesh = MR::MeshLoad::fromAnySupportedFormat("in.obj").value(); +MR::DecimateSettings s; +s.maxDeletedFaces = int(mesh.topology.numValidFaces() / 2); +MR::decimateMesh(mesh, s); +\endcode + +## Signed distance from a point to a mesh + +libigl: +\code{.cpp} +#include + +Eigen::VectorXd S; +Eigen::VectorXi I; +Eigen::MatrixXd C, N; +igl::signed_distance(P, V, F, igl::SIGNED_DISTANCE_TYPE_PSEUDONORMAL, S, I, C, N); +\endcode + +MeshLib: +\code{.cpp} +#include +#include + +auto sd = MR::findSignedDistance(point, mesh); +float dist = sd->dist; +\endcode + +# Open3D + +## Point-to-point ICP registration + +Open3D: +\code{.cpp} +#include +using namespace open3d; + +auto src = io::CreatePointCloudFromFile("src.ply"); +auto tgt = io::CreatePointCloudFromFile("tgt.ply"); +auto result = pipelines::registration::RegistrationICP( + *src, *tgt, /*maxCorrDist=*/0.05, + Eigen::Matrix4d::Identity(), + pipelines::registration::TransformationEstimationPointToPoint()); +Eigen::Matrix4d xf = result.transformation_; +\endcode + +MeshLib: +\code{.cpp} +#include +#include +#include + +auto src = MR::PointsLoad::fromAnySupportedFormat("src.ply").value(); +auto tgt = MR::PointsLoad::fromAnySupportedFormat("tgt.ply").value(); +MR::ICP icp( {src}, {tgt}, MR::AffineXf3f{}, MR::AffineXf3f{}, /*samplingVoxel=*/0.05f ); +auto xf = icp.calculateTransformation(); +\endcode + +## Point cloud to mesh (surface reconstruction) + +Open3D (Ball Pivoting): +\code{.cpp} +#include +using namespace open3d; + +auto pcd = io::CreatePointCloudFromFile("cloud.ply"); +pcd->EstimateNormals(); +std::vector radii = {0.005, 0.01, 0.02, 0.04}; +auto mesh = geometry::TriangleMesh::CreateFromPointCloudBallPivoting(*pcd, radii); +\endcode + +MeshLib (voxel fusion): +\code{.cpp} +#include +#include +#include +#include + +auto cloud = MR::PointsLoad::fromAnySupportedFormat("cloud.ply").value(); +MR::PointsToMeshParameters p; +p.voxelSize = cloud.computeBoundingBox().diagonal() * 1e-2f; +MR::Mesh mesh = MR::pointsToMeshFusion(cloud, p).value(); +\endcode + +# PCL + +## Voxel-grid downsampling of a point cloud + +PCL: +\code{.cpp} +#include +#include + +pcl::PointCloud::Ptr cloud(new pcl::PointCloud); +pcl::io::loadPCDFile("cloud.pcd", *cloud); +pcl::VoxelGrid vg; +vg.setInputCloud(cloud); +vg.setLeafSize(0.05f, 0.05f, 0.05f); +pcl::PointCloud::Ptr out(new pcl::PointCloud); +vg.filter(*out); +\endcode + +MeshLib: +\code{.cpp} +#include +#include +#include + +// cloud.ply saved once from PCL: pcl::io::savePLYFile("cloud.ply", *cloud) +auto cloud = MR::PointsLoad::fromAnySupportedFormat("cloud.ply").value(); +MR::UniformSamplingSettings s; +s.distance = 0.05f; +MR::PointCloud out = MR::makeUniformSampledCloud(cloud, s).value(); +\endcode + +## Estimate point-cloud normals + +PCL: +\code{.cpp} +#include + +pcl::NormalEstimation ne; +ne.setInputCloud(cloud); +pcl::search::KdTree::Ptr tree(new pcl::search::KdTree); +ne.setSearchMethod(tree); +ne.setKSearch(20); +pcl::PointCloud::Ptr normals(new pcl::PointCloud); +ne.compute(*normals); +\endcode + +MeshLib: +\code{.cpp} +#include + +auto normals = MR::makeNormals(cloud, /*avgNeighborhoodSize=*/20); +cloud.normals = normals; +\endcode + +# VTK + +MeshLib and VTK exchange data through standard mesh formats: write STL / PLY / OBJ from your VTK pipeline and load it in MeshLib — and the same way back. +In Python the bridge is even shorter: vtkPolyData arrays go straight into MeshLib via numpy, no files at all (last example below). + +## Decimate a mesh + +VTK: +\code{.cpp} +#include +#include +#include +#include + +auto reader = vtkSmartPointer::New(); +reader->SetFileName("in.vtk"); +reader->Update(); + +auto dec = vtkSmartPointer::New(); +dec->SetInputData(reader->GetOutput()); +dec->SetTargetReduction(0.5); +dec->Update(); +vtkPolyData* out = dec->GetOutput(); +\endcode + +MeshLib: +\code{.cpp} +#include +#include +#include + +// in.stl exported from the VTK pipeline (vtkSTLWriter / vtkPLYWriter / vtkOBJWriter) +auto mesh = MR::MeshLoad::fromAnySupportedFormat("in.stl").value(); +MR::DecimateSettings s; +s.maxDeletedFaces = int(mesh.topology.numValidFaces() / 2); +MR::decimateMesh(mesh, s); +\endcode + +## Boolean union of two meshes + +VTK: +\code{.cpp} +#include +#include +#include + +auto ra = vtkSmartPointer::New(); ra->SetFileName("a.vtk"); ra->Update(); +auto rb = vtkSmartPointer::New(); rb->SetFileName("b.vtk"); rb->Update(); + +auto boolOp = vtkSmartPointer::New(); +boolOp->SetOperationToUnion(); +boolOp->SetInputData(0, ra->GetOutput()); +boolOp->SetInputData(1, rb->GetOutput()); +boolOp->Update(); +vtkPolyData* out = boolOp->GetOutput(); +\endcode + +MeshLib: +\code{.cpp} +#include +#include + +// a.stl / b.stl exported from VTK (vtkSTLWriter) +auto a = MR::MeshLoad::fromAnySupportedFormat("a.stl").value(); +auto b = MR::MeshLoad::fromAnySupportedFormat("b.stl").value(); +auto res = MR::boolean(a, b, MR::BooleanOperation::Union); +MR::Mesh out = res.mesh; +\endcode + +## Read and write mesh files + +VTK: +\code{.cpp} +#include +#include +#include + +auto reader = vtkSmartPointer::New(); +reader->SetFileName("in.stl"); +reader->Update(); + +auto writer = vtkSmartPointer::New(); +writer->SetFileName("out.obj"); +writer->SetInputData(reader->GetOutput()); +writer->Write(); +\endcode + +MeshLib: +\code{.cpp} +#include +#include + +auto mesh = MR::MeshLoad::fromAnySupportedFormat("in.stl").value(); +MR::MeshSave::toAnySupportedFormat(mesh, "out.obj"); +\endcode + +## Moving data in memory (Python) + +In Python no files are needed: vtkPolyData arrays go straight into MeshLib via numpy and back. +\code{.py} +from vtk.util.numpy_support import vtk_to_numpy +from meshlib import mrmeshpy, mrmeshnumpy + +# vtkPolyData -> MeshLib (triangulated; run vtkTriangleFilter first if needed) +verts = vtk_to_numpy(poly.GetPoints().GetData()) +faces = vtk_to_numpy(poly.GetPolys().GetConnectivityArray()).reshape(-1, 3) +mesh = mrmeshnumpy.meshFromFacesVerts(faces, verts) + +# ... any MeshLib processing: boolean, offset, decimate ... + +# MeshLib -> numpy (rebuild vtkPolyData with numpy_to_vtk) +out_verts = mrmeshnumpy.getNumpyVerts(mesh) +out_faces = mrmeshnumpy.getNumpyFaces(mesh.topology) +\endcode + +# Missing your case? + +Don't see your library or your operation? +[Open an issue](https://github.com/MeshInspector/MeshLib/issues) or [start a GitHub Discussion](https://github.com/MeshInspector/MeshLib/discussions) — we'll add the snippet. + +*/ diff --git a/doxygen/layout_templates/base_struct.xml b/doxygen/layout_templates/base_struct.xml index 16231fde07bf..dd48abe8d38f 100644 --- a/doxygen/layout_templates/base_struct.xml +++ b/doxygen/layout_templates/base_struct.xml @@ -63,6 +63,7 @@ +