From ed0b58b0248089c9451014bd434e3b9eee134af0 Mon Sep 17 00:00:00 2001 From: Egor Mikhaylov Date: Wed, 4 Sep 2024 18:38:42 +0500 Subject: [PATCH] Green tests! (#3297) * Green tests! * Add `MR_BIND_TEMPLATE`. Fail the build if the tests fail. --- .github/workflows/build-test-ubuntu-x64.yml | 4 +- scripts/mrbind/compiler_only_flags.txt | 2 +- scripts/mrbind/generate.mk | 15 ++- scripts/mrbind/helpers.cpp | 117 ++++++++++++++++++++ scripts/mrbind/mrbind_flags.txt | 2 +- scripts/mrbind/mrbind_flags_for_helpers.txt | 4 + source/MRMesh/MRBindingMacros.h | 20 ++++ source/MRMesh/MRContour.h | 11 ++ source/MRMesh/MRMesh.vcxproj | 1 + source/MRMesh/MRMesh.vcxproj.filters | 9 +- test_python/test_voxels_conversion.py | 14 ++- 11 files changed, 186 insertions(+), 13 deletions(-) create mode 100644 scripts/mrbind/helpers.cpp create mode 100644 scripts/mrbind/mrbind_flags_for_helpers.txt create mode 100644 source/MRMesh/MRBindingMacros.h diff --git a/.github/workflows/build-test-ubuntu-x64.yml b/.github/workflows/build-test-ubuntu-x64.yml index 6d2cff8ee52c..d2265aea7965 100644 --- a/.github/workflows/build-test-ubuntu-x64.yml +++ b/.github/workflows/build-test-ubuntu-x64.yml @@ -121,7 +121,7 @@ jobs: repository: MeshInspector/mrbind path: _mrbind token: ${{ secrets.BUILD_MACHINE_TOKEN }} - ref: 83a53a39c1f0dbb5564a47355026406ead99946b + ref: b8448867e913efe0561404597b575588a20b674b - name: Compile MRBind if: ${{ matrix.os == 'ubuntu24' }} @@ -170,7 +170,7 @@ jobs: LD_LIBRARY_PATH: . USE_MESHLIB2_PY: 1 working-directory: build/${{ matrix.config }}/bin - run: python3 ./../../../scripts/run_python_test_script.py -d '../test_python' || true + run: python3 ./../../../scripts/run_python_test_script.py -d '../test_python' - name: Collect Timings run: ./scripts/devops/collect_timing_logs.sh ${{matrix.os}} ${{matrix.config}} "${{matrix.compiler}}" diff --git a/scripts/mrbind/compiler_only_flags.txt b/scripts/mrbind/compiler_only_flags.txt index 98353e9b90cc..00214ca95e63 100644 --- a/scripts/mrbind/compiler_only_flags.txt +++ b/scripts/mrbind/compiler_only_flags.txt @@ -1 +1 @@ --I_mrbind/include -Wno-deprecated-declarations -Wno-implicitly-unsigned-literal -fPIC -DMR_COMPILING_PB11_BINDINGS -DMRBIND_HEADER='' -DMB_PB11_MODULE_NAME=mrmeshpy -DMB_PB11_ADJUST_NAMES='"s/\\bMR:://g;s/\\bvec_\\b/vec/"' -DMB_PB11_ENABLE_CXX_STYLE_CONTAINER_METHODS +-I_mrbind/include -Wno-deprecated-declarations -Wno-implicitly-unsigned-literal -fPIC -DMR_COMPILING_PB11_BINDINGS -DMRBIND_HEADER='' -DMB_PB11_MODULE_NAME=mrmeshpy -DMB_PB11_ADJUST_NAMES='"s/\\bMR::Extra:://g;s/\\bMR:://g;s/\\bvec_\\b/vec/"' -DMB_PB11_ENABLE_CXX_STYLE_CONTAINER_METHODS diff --git a/scripts/mrbind/generate.mk b/scripts/mrbind/generate.mk index 654b31adaffa..042aaff6e296 100644 --- a/scripts/mrbind/generate.mk +++ b/scripts/mrbind/generate.mk @@ -28,14 +28,15 @@ DEPS_BASE_DIR := . # ] MODULE_OUTPUT_DIR := $(MESHLIB_SHLIB_DIR)/meshlib2 -MRBIND_FLAGS := $(file <$(makefile_dir)/mrbind_flags.txt) # Those variables are for mrbind/scripts/apply_to_files.mk INPUT_DIRS := $(addprefix $(makefile_dir)/../../source/,MRMesh MRSymbolMesh) $(makefile_dir) INPUT_FILES_BLACKLIST := $(file <$(makefile_dir)/input_file_blacklist.txt) OUTPUT_DIR := build/binds INPUT_GLOBS := *.h -MRBIND := $(MRBIND_EXE) $(MRBIND_FLAGS) +MRBIND := $(MRBIND_EXE) +MRBIND_FLAGS := $(file <$(makefile_dir)/mrbind_flags.txt) +MRBIND_FLAGS_FOR_EXTRA_INPUTS := $(file <$(makefile_dir)/mrbind_flags_for_helpers.txt) COMPILER_FLAGS := $(file <$(makefile_dir)/common_compiler_parser_flags.txt) $(shell pkg-config --cflags python3-embed) -I. -I$(DEPS_BASE_DIR)/include -I$(makefile_dir)/../../source COMPILER_FLAGS_LIBCLANG := $(file <$(makefile_dir)/parser_only_flags.txt) COMPILER := $(CXX) $(file <$(makefile_dir)/compiler_only_flags.txt) -I$(MRBIND_SOURCE)/include @@ -43,6 +44,7 @@ LINKER_OUTPUT := $(MODULE_OUTPUT_DIR)/mrmeshpy$(shell python3-config --extension LINKER := $(CXX) -fuse-ld=lld LINKER_FLAGS := -Wl,-rpath='$$ORIGIN/..:$$ORIGIN' $(shell pkg-config --libs python3-embed) -L$(DEPS_BASE_DIR)/lib -L$(MESHLIB_SHLIB_DIR) -lMRMesh -lMRSymbolMesh -shared $(file <$(makefile_dir)/linker_flags.txt) NUM_FRAGMENTS := 4 +EXTRA_INPUT_SOURCES := $(makefile_dir)/helpers.cpp override mrbind_vars = $(subst $,$$$$, \ INPUT_DIRS=$(call quote,$(INPUT_DIRS)) \ @@ -50,6 +52,8 @@ override mrbind_vars = $(subst $,$$$$, \ OUTPUT_DIR=$(call quote,$(OUTPUT_DIR)) \ INPUT_GLOBS=$(call quote,$(INPUT_GLOBS)) \ MRBIND=$(call quote,$(MRBIND)) \ + MRBIND_FLAGS=$(call quote,$(MRBIND_FLAGS)) \ + MRBIND_FLAGS_FOR_EXTRA_INPUTS=$(call quote,$(MRBIND_FLAGS_FOR_EXTRA_INPUTS)) \ COMPILER_FLAGS=$(call quote,$(COMPILER_FLAGS)) \ COMPILER_FLAGS_LIBCLANG=$(call quote,$(COMPILER_FLAGS_LIBCLANG)) \ COMPILER=$(call quote,$(COMPILER)) \ @@ -57,6 +61,7 @@ override mrbind_vars = $(subst $,$$$$, \ LINKER=$(call quote,$(LINKER)) \ LINKER_FLAGS=$(call quote,$(LINKER_FLAGS)) \ NUM_FRAGMENTS=$(call quote,$(NUM_FRAGMENTS)) \ + EXTRA_INPUT_SOURCES=$(call quote,$(EXTRA_INPUT_SOURCES)) \ ) # Generated mrmeshpy. @@ -69,8 +74,8 @@ only-generate: $(MAKE) -f $(MRBIND_SOURCE)/scripts/apply_to_files.mk generate $(mrbind_vars) # Handwritten mrmeshnumpy. -MRMESHPY_MODULE := $(MODULE_OUTPUT_DIR)/mrmeshnumpy$(shell python3-config --extension-suffix) -$(MRMESHPY_MODULE): | $(MODULE_OUTPUT_DIR) +MRMESHNUMPY_MODULE := $(MODULE_OUTPUT_DIR)/mrmeshnumpy$(shell python3-config --extension-suffix) +$(MRMESHNUMPY_MODULE): | $(MODULE_OUTPUT_DIR) $(CXX) \ -o $@ \ $(makefile_dir)/../../source/mrmeshnumpy/*.cpp \ @@ -82,7 +87,7 @@ $(MRMESHPY_MODULE): | $(MODULE_OUTPUT_DIR) # All modules. .DEFAULT_GOAL := all .PHONY: all -all: $(LINKER_OUTPUT) $(MRMESHPY_MODULE) +all: $(LINKER_OUTPUT) $(MRMESHNUMPY_MODULE) # The directory for the modules. $(MODULE_OUTPUT_DIR): diff --git a/scripts/mrbind/helpers.cpp b/scripts/mrbind/helpers.cpp new file mode 100644 index 000000000000..d78d889f2fb6 --- /dev/null +++ b/scripts/mrbind/helpers.cpp @@ -0,0 +1,117 @@ +#include "MRMesh/MRBitSetParallelFor.h" +#include "MRMesh/MRBoolean.h" +#include "MRMesh/MREdgeIterator.h" +#include "MRMesh/MRMesh.h" +#include "MRMesh/MRPointsToMeshProjector.h" + +// Only the functions that should be exported should be in `MR::Extra`. Place everything else somewhere outside. +// Note that the comments are pasted to Python too. + +namespace MR::Extra +{ + // Fix self-intersections by converting to voxels and back. + void fixSelfIntersections( Mesh& mesh, float voxelSize ) + { + MeshVoxelsConverter convert; + convert.voxelSize = voxelSize; + auto gridA = convert(mesh); + mesh = convert(gridA); + } + + // Subtract mesh B from mesh A. + Mesh voxelBooleanSubtract( const Mesh& mesh1, const Mesh& mesh2, float voxelSize ) + { + MeshVoxelsConverter convert; + convert.voxelSize = voxelSize; + auto gridA = convert(mesh1); + auto gridB = convert(mesh2); + gridA -= gridB; + return convert(gridA); + } + + // Unite mesh A and mesh B. + Mesh voxelBooleanUnite( const Mesh& mesh1, const Mesh& mesh2, float voxelSize ) + { + MeshVoxelsConverter convert; + convert.voxelSize = voxelSize; + auto gridA = convert(mesh1); + auto gridB = convert(mesh2); + gridA += gridB; + return convert( gridA ); + } + + // Intersect mesh A and mesh B. + Mesh voxelBooleanIntersect( const Mesh& mesh1, const Mesh& mesh2, float voxelSize ) + { + MeshVoxelsConverter convert; + convert.voxelSize = voxelSize; + auto gridA = convert(mesh1); + auto gridB = convert(mesh2); + gridA *= gridB; + return convert( gridA ); + } + + // Computes signed distances from all mesh points to refMesh. + // `refMesh` - all points will me projected to this mesh + // `mesh` - this mesh points will be projected + // `refXf` - world transform for refMesh + // `upDistLimitSq` - upper limit on the distance in question, if the real distance is larger than the returning upDistLimit + // `loDistLimitSq` - low limit on the distance in question, if a point is found within this distance then it is immediately returned without searching for a closer one + VertScalars projectAllMeshVertices( const Mesh& refMesh, const Mesh& mesh, const AffineXf3f* refXf = nullptr, const AffineXf3f* xf = nullptr, float upDistLimitSq = FLT_MAX, float loDistLimitSq = 0.0f ) + { + PointsToMeshProjector projector; + projector.updateMeshData( &refMesh ); + std::vector mpRes( mesh.points.vec_.size() ); + projector.findProjections( mpRes, mesh.points.vec_, xf, refXf, upDistLimitSq, loDistLimitSq ); + VertScalars res( mesh.topology.lastValidVert() + 1, std::sqrt( upDistLimitSq ) ); + + AffineXf3f fullXf; + if ( refXf ) + fullXf = refXf->inverse(); + if ( xf ) + fullXf = fullXf * ( *xf ); + + BitSetParallelFor( mesh.topology.getValidVerts(), [&] ( VertId v ) + { + const auto& mpResV = mpRes[v.get()]; + auto& resV = res[v]; + + resV = mpResV.distSq; + if ( mpResV.mtp.e ) + resV = refMesh.signedDistance( fullXf( mesh.points[v] ), mpResV ); + else + resV = std::sqrt( resV ); + } ); + return res; + } + + // Merge a list of meshes to one mesh. + Mesh mergeMeshes( const std::vector>& meshes ) + { + Mesh res; + for ( const auto& m : meshes ) + res.addPart( *m ); + return res; + } + + // Return faces with at least one edge longer than the specified length. + FaceBitSet getFacesByMinEdgeLength( const Mesh& mesh, float minLength ) + { + using namespace MR; + FaceBitSet resultFaces( mesh.topology.getValidFaces().size() ); + float minLengthSq = minLength * minLength; + for ( auto ue : undirectedEdges( mesh.topology ) ) + { + if ( mesh.edgeLengthSq( ue ) > minLengthSq ) + { + auto l = mesh.topology.left( ue ); + auto r = mesh.topology.right( ue ); + if ( l ) + resultFaces.set( l ); + if ( r ) + resultFaces.set( r ); + } + } + return resultFaces; + } +} diff --git a/scripts/mrbind/mrbind_flags.txt b/scripts/mrbind/mrbind_flags.txt index 76f3b57f8647..3ee4e8e68888 100644 --- a/scripts/mrbind/mrbind_flags.txt +++ b/scripts/mrbind/mrbind_flags.txt @@ -5,8 +5,8 @@ --ignore MR::detail --ignore MR::Signal --ignore MR::UniquePtr ---ignore MR::OpenVdbFloatGrid --ignore MR::RegisterRenderObjectConstructor --ignore MR::Config --allow std::integral_constant --skip-base boost::dynamic_bitset +--skip-base /openvdb::v[0-9][0-9_a-zA-Z]*::Grid/ diff --git a/scripts/mrbind/mrbind_flags_for_helpers.txt b/scripts/mrbind/mrbind_flags_for_helpers.txt new file mode 100644 index 000000000000..27e7ca209900 --- /dev/null +++ b/scripts/mrbind/mrbind_flags_for_helpers.txt @@ -0,0 +1,4 @@ +--format=macros +--ignore-pch-flags +--ignore :: +--allow MR::Extra diff --git a/source/MRMesh/MRBindingMacros.h b/source/MRMesh/MRBindingMacros.h new file mode 100644 index 000000000000..68b735f998e0 --- /dev/null +++ b/source/MRMesh/MRBindingMacros.h @@ -0,0 +1,20 @@ +#pragma once + +// Those macros help control Python bindings generated using MRBind. + +// MR_PARSING_FOR_PB11_BINDINGS - gets defined when parsing the source code +// MR_COMPILING_PB11_BINDINGS - gets defined when compiling the resulting bindings + +// Use to specify valid template arguments for templates (usually function templates). +// For example: +// template void foo(T) {...}; +// MR_BIND_TEMPLATE( void foo(int) ) +// MR_BIND_TEMPLATE( void foo(float) ) +// +// As with `extern template`, you might need to use `foo<...>` instead of `foo` if the template parameters can't be deduced from the +// parameter types and the return type. +#ifdef MR_PARSING_FOR_PB11_BINDINGS +#define MR_BIND_TEMPLATE(...) extern template __VA_ARGS__; +#else +#define MR_BIND_TEMPLATE(...) +#endif diff --git a/source/MRMesh/MRContour.h b/source/MRMesh/MRContour.h index 46f52bb79852..482d56106f5d 100644 --- a/source/MRMesh/MRContour.h +++ b/source/MRMesh/MRContour.h @@ -1,5 +1,6 @@ #pragma once +#include "MRBindingMacros.h" #include "MRMeshFwd.h" #include "MRVector3.h" @@ -77,4 +78,14 @@ To copyContours( const From & from ) /// \} +// Instantiate the templates when generating bindings. +MR_BIND_TEMPLATE( float calcOrientedArea( const Contour2 & contour ) ) +MR_BIND_TEMPLATE( double calcOrientedArea( const Contour2 & contour ) ) +MR_BIND_TEMPLATE( Vector3 calcOrientedArea( const Contour3 & contour ) ) +MR_BIND_TEMPLATE( Vector3 calcOrientedArea( const Contour3 & contour ) ) +MR_BIND_TEMPLATE( Contour2 copyContour( const Contour2 & from ) ) +MR_BIND_TEMPLATE( Contour3 copyContour( const Contour3 & from ) ) +MR_BIND_TEMPLATE( Contour2 copyContour( const Contour2 & from ) ) +MR_BIND_TEMPLATE( Contour3 copyContour( const Contour3 & from ) ) + } // namespace MR diff --git a/source/MRMesh/MRMesh.vcxproj b/source/MRMesh/MRMesh.vcxproj index 123c33b40ba8..1e57bc03bf1b 100644 --- a/source/MRMesh/MRMesh.vcxproj +++ b/source/MRMesh/MRMesh.vcxproj @@ -418,6 +418,7 @@ + diff --git a/source/MRMesh/MRMesh.vcxproj.filters b/source/MRMesh/MRMesh.vcxproj.filters index 61c675ab051b..081a9099b7ca 100644 --- a/source/MRMesh/MRMesh.vcxproj.filters +++ b/source/MRMesh/MRMesh.vcxproj.filters @@ -1335,7 +1335,12 @@ Source Files\MeshAlgorithm - + + Source Files\Basic + + + Source Files\Basic + @@ -2226,4 +2231,4 @@ Source Files\Python - \ No newline at end of file + diff --git a/test_python/test_voxels_conversion.py b/test_python/test_voxels_conversion.py index b3d3be04a451..eb1f647b5409 100644 --- a/test_python/test_voxels_conversion.py +++ b/test_python/test_voxels_conversion.py @@ -36,8 +36,18 @@ def test_voxels_conversion(): # Test mesh build functions radius = 8 * mul - mesh1 = mrmesh.gridToMesh(vdb_volume, isoValue = max_value - radius * radius) - mesh2 = mrmesh.gridToMesh(grid, voxelSize = volume0.voxelSize, isoValue = max_value - radius * radius) + if is_new_binding: + settings1 = mrmesh.GridToMeshSettings() + settings1.isoValue = max_value - radius * radius + settings1.voxelSize = vdb_volume.voxelSize + mesh1 = mrmesh.gridToMesh(vdb_volume.data, settings1) + settings2 = mrmesh.GridToMeshSettings() + settings2.isoValue = max_value - radius * radius + settings2.voxelSize = volume0.voxelSize + mesh2 = mrmesh.gridToMesh(grid, settings2) + else: + mesh1 = mrmesh.gridToMesh(vdb_volume, isoValue = max_value - radius * radius) + mesh2 = mrmesh.gridToMesh(grid, voxelSize = volume0.voxelSize, isoValue = max_value - radius * radius) # Basic checks for mesh in (mesh1, mesh2): assert len(mrmesh.getAllComponents(mesh1)) == 1