Skip to content

Commit 803be5e

Browse files
authored
added boolean operation in meshconv (union, subtract, Intersection) (#166)
* added boolean operation in meshconv (union, subtract, Intersection) * reworked. fixed parsing
1 parent 8a45cc5 commit 803be5e

File tree

2 files changed

+105
-24
lines changed

2 files changed

+105
-24
lines changed

source/MREAlgorithms/MREBooleanOperation.h

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
namespace MRE
88
{
99

10-
/** \ingroup BooleanGroup
10+
/// \addtogroup BooleanGroup
11+
/// \{
12+
13+
/**
1114
* Enum class of available CSG operations
1215
* \image html boolean/no_bool.png "Two separate meshes" width = 300cm
1316
* \sa \ref MRE::boolean
@@ -42,10 +45,9 @@ enum class BooleanOperation
4245
Count ///< not a valid operation
4346
};
4447

45-
/// Structure to map old mesh BitSets to new
4648
/** \struct MRE::BooleanResultMapper
47-
* \ingroup BooleanGroup
48-
* \brief Structure to easily map topology of MRE::boolean input meshes to result mesh
49+
* \brief Structure to map old mesh BitSets to new
50+
* \details Structure to easily map topology of MRE::boolean input meshes to result mesh
4951
*
5052
* This structure allows to map faces, vertices and edges of mesh `A` and mesh `B` input of MRE::boolean to result mesh topology primitives
5153
* \sa \ref MRE::boolean
@@ -81,13 +83,14 @@ struct BooleanResultMapper
8183
std::array<Maps, size_t( MapObject::Count )> maps;
8284
};
8385

84-
85-
// Perform boolean operation on cut meshes,
86-
// returns mesh in space of meshA or error.
87-
// note: actually this function is meant to be internal, use "boolean" instead
86+
/// Perform boolean operation on cut meshes
87+
/// \return mesh in space of meshA or error.
88+
/// \note: actually this function is meant to be internal, use "boolean" instead
8889
MREALGORITHMS_API tl::expected<MR::Mesh, std::string> doBooleanOperation( const MR::Mesh& meshACut, const MR::Mesh& meshBCut,
8990
const std::vector<MR::EdgePath>& cutEdgesA, const std::vector<MR::EdgePath>& cutEdgesB,
9091
BooleanOperation operation, const MR::AffineXf3f* rigidB2A = nullptr,
9192
BooleanResultMapper* mapper = nullptr );
9293

94+
/// \}
95+
9396
}

source/meshconv/meshconv.cpp

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,120 @@
22
#include "MRMesh/MRMeshLoad.h"
33
#include "MRMesh/MRMeshSave.h"
44
#include "MREAlgorithms/MREMeshDecimate.h"
5+
#include "MREAlgorithms/MREMeshBoolean.h"
56
#include <boost/program_options.hpp>
67
#include <boost/exception/diagnostic_information.hpp>
78
#include <iostream>
89

10+
bool doCommand( const boost::program_options::option& option, MR::Mesh& mesh )
11+
{
12+
namespace po = boost::program_options;
13+
if ( option.string_key == "remesh" )
14+
{
15+
float targetEdgeLen{ 0.f };
16+
if ( !option.value.empty() )
17+
targetEdgeLen = std::stof( option.value[0] );
18+
if ( targetEdgeLen <= 0 )
19+
targetEdgeLen = mesh.averageEdgeLength();
20+
21+
MRE::RemeshSettings rems;
22+
rems.targetEdgeLen = targetEdgeLen;
23+
rems.maxDeviation = targetEdgeLen / 100;
24+
MRE::remesh( mesh, rems );
25+
26+
std::cout << "re-meshed successfully to target edge length " << targetEdgeLen << "\n";
27+
}
28+
else if ( option.string_key == "unite" || option.string_key == "subtract" || option.string_key == "intersect" )
29+
{
30+
std::filesystem::path meshPath = option.value[0];
31+
32+
auto loadRes = MR::MeshLoad::fromAnySupportedFormat( meshPath );
33+
if ( !loadRes.has_value() )
34+
{
35+
std::cerr << "Mesh load error: " << loadRes.error() << "\n";
36+
return false;
37+
}
38+
auto meshB = std::move( loadRes.value() );
39+
std::cout << meshPath << " loaded successfully\n";
40+
41+
MRE::BooleanOperation bo{ MRE::BooleanOperation::Union };
42+
if ( option.string_key == "subtract" )
43+
bo = MRE::BooleanOperation::OutsideA;
44+
if ( option.string_key == "intersect" )
45+
bo = MRE::BooleanOperation::Intersection;
46+
auto booleanRes = MRE::boolean( mesh, meshB, bo );
47+
48+
if ( !booleanRes )
49+
{
50+
std::cerr << booleanRes.errorString << "\n";
51+
return false;
52+
}
53+
else
54+
{
55+
std::cout << option.string_key << " success!\n";
56+
mesh = std::move( booleanRes.mesh );
57+
}
58+
}
59+
return true;
60+
}
61+
962
// can throw
1063
static int mainInternal( int argc, char **argv )
1164
{
1265
std::filesystem::path inFilePath;
1366
std::filesystem::path outFilePath;
14-
float targetEdgeLen = 0;
1567

1668
namespace po = boost::program_options;
17-
po::options_description desc( "Available options" );
18-
desc.add_options()
69+
po::options_description generalOptions( "General options" );
70+
generalOptions.add_options()
1971
("help", "produce help message")
2072
("input-file", po::value<std::filesystem::path>( &inFilePath ), "filename of input mesh")
2173
("output-file", po::value<std::filesystem::path>( &outFilePath ), "filename of output mesh")
22-
("remesh", po::value<float>( &targetEdgeLen )->implicit_value( targetEdgeLen ), "optional argument if positive is target edge length after remeshing")
2374
;
2475

76+
po::options_description commands( "Commands" );
77+
commands.add_options()
78+
( "remesh", po::value<float>()->implicit_value( 0 ), "optional argument if positive is target edge length after remeshing" )
79+
( "unite", po::value<std::filesystem::path>(), "unite mesh from input file and given mesh" )
80+
( "subtract", po::value<std::filesystem::path>(), "subtract given mesh from input file mesh given mesh" )
81+
( "intersect", po::value<std::filesystem::path>(), "intersect mesh from input file and given mesh" )
82+
;
83+
84+
po::options_description allCommands( "Available options" );
85+
allCommands.add( generalOptions ).add( commands );
86+
2587
po::positional_options_description p;
2688
p.add("input-file", 1);
2789
p.add("output-file", 1);
2890

91+
po::parsed_options parsedGeneral = po::command_line_parser( argc, argv )
92+
.options( generalOptions )
93+
.positional( p )
94+
.allow_unregistered()
95+
.run();
96+
97+
std::vector<std::string> unregisteredOptions;
98+
for ( const auto& o : parsedGeneral.options )
99+
{
100+
if ( o.unregistered )
101+
unregisteredOptions.insert( unregisteredOptions.end(), o.original_tokens.begin(), o.original_tokens.end() );
102+
}
103+
29104
po::variables_map vm;
30-
po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
105+
po::store(parsedGeneral, vm);
31106
po::notify(vm);
32107

108+
po::parsed_options parsedCommands = po::command_line_parser( unregisteredOptions )
109+
.options( commands )
110+
.allow_unregistered()
111+
.run();
112+
33113
if ( vm.count("help") || !vm.count("input-file") || !vm.count("output-file") )
34114
{
35115
std::cerr <<
36116
"meshconv is mesh file conversion utility based on MeshInspector/MeshLib\n"
37117
"Usage: meshconv input-file output-file [options]\n"
38-
<< desc << "\n";
118+
<< allCommands << "\n";
39119
return 1;
40120
}
41121

@@ -48,19 +128,17 @@ static int mainInternal( int argc, char **argv )
48128
auto mesh = std::move( loadRes.value() );
49129
std::cout << inFilePath << " loaded successfully\n";
50130

51-
if ( vm.count("remesh") )
131+
std::vector<std::vector<std::string>> lists;
132+
for ( const po::option& o : parsedCommands.options )
52133
{
53-
if ( targetEdgeLen <= 0 )
54-
targetEdgeLen = mesh.averageEdgeLength();
55-
56-
MRE::RemeshSettings rems;
57-
rems.targetEdgeLen = targetEdgeLen;
58-
rems.maxDeviation = targetEdgeLen / 100;
59-
MRE::remesh( mesh, rems );
60-
61-
std::cout << "re-meshed successfully to target edge length " << targetEdgeLen << "\n";
134+
if ( !doCommand( o, mesh ) )
135+
{
136+
std::cerr << "Error in command : \""<< o.string_key << " " << o.value[0] << "\"\nBreak\n";
137+
return 1;
138+
}
62139
}
63140

141+
64142
auto saveRes = MR::MeshSave::toAnySupportedFormat( mesh, outFilePath );
65143
if ( !saveRes.has_value() )
66144
{

0 commit comments

Comments
 (0)