openscad-cpp-evaluator 1.24.0__tar.gz → 1.26.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/CLAUDE.md +49 -1
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/colored_body.hpp +9 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/evaluator.hpp +3 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/mesh_check.hpp +13 -3
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/builtins.hpp +4 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/function_builtins.cpp +1 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/import.cpp +23 -2
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/primitives_3d.cpp +9 -1
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/registry.cpp +4 -1
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/topology.cpp +112 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/colored_body.cpp +12 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/csg_generate.cpp +24 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/mesh_check.cpp +11 -6
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_booleans.cpp +96 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_csg_tree.cpp +21 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_python_bindings.py +50 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/README.md +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/coverage.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/format_closure.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/utf8.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_coverage.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_feature_detection.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_include_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/uv.lock +0 -0
|
@@ -946,6 +946,40 @@ grep for `ponytail:`.
|
|
|
946
946
|
shut. Note the reduction is NOT monotonic in the tolerance (1.0 gave *more* triangles than 0.5 on
|
|
947
947
|
the test model, since simplification can force re-triangulation), so don't read a jump as a bug.
|
|
948
948
|
|
|
949
|
+
- `mesh_repair()` (also `topology.cpp`) — makes a broken mesh watertight, so geometry that was
|
|
950
|
+
merely *almost* closed can take part in CSG. Another BelfrySCAD extension: nothing in the language
|
|
951
|
+
could repair geometry, and a mesh from a library generator or an import is routinely a few
|
|
952
|
+
boundary edges short of a solid. The work is `repairMesh()`'s, already used by
|
|
953
|
+
`import(..., repair=true)`; this exposes the same six steps to any geometry — weld, drop
|
|
954
|
+
degenerate/duplicate, orient, fill holes, flip outward, strip zero-area faces.
|
|
955
|
+
|
|
956
|
+
**It reads `ColoredBody::rawMesh`, not `body`.** An open `polyhedron()` cannot build a Manifold,
|
|
957
|
+
so it keeps the raw triangle soup there and leaves `body` set but EMPTY. Repairing the empty
|
|
958
|
+
Manifold finds nothing to do and reports success at having done nothing, which is how the first
|
|
959
|
+
version of this failed silently. On success the soup is cleared, or `splitByRole()` keeps pulling
|
|
960
|
+
the body aside as un-CSG-able — the very thing the repair just fixed.
|
|
961
|
+
|
|
962
|
+
**The default tolerance is `GRID_FINE` (2⁻²⁰ ≈ 9.54e-7)**, OpenSCAD's own constant for deciding
|
|
963
|
+
two points are one, so a mesh that welds there welds here; it is exact in binary, so the
|
|
964
|
+
multiply-and-round in `weldMap()` adds no error. Deliberately tight, unlike `simplify()`'s
|
|
965
|
+
scale-relative default, because welding DISCARDS vertices: a default that quietly merged real
|
|
966
|
+
detail would be worse than one that leaves a mesh open, and hole filling closes what welding
|
|
967
|
+
declines to. `import(..., repair=true)` takes the same `tolerance=` argument for the same reason.
|
|
968
|
+
|
|
969
|
+
**Watertight is not the same as clean**, and the tolerance chooses. A gap whose rim is two
|
|
970
|
+
vertices that should be one closes either by welding (the duplicate disappears, no new geometry)
|
|
971
|
+
or by filling (the pair survives and step 4 stitches a triangle across). The filled patch is a
|
|
972
|
+
*needle* — two corners a hair apart — and step 6 will not remove it, because it strips
|
|
973
|
+
genuinely zero-area faces and a needle across a 2e-5 gap with its third corner 0.25 away has an
|
|
974
|
+
area around 2.5e-6. Thin, not degenerate. So a repaired-but-ugly mesh means the tolerance was too
|
|
975
|
+
small; the `polyhedron()` warning names a boundary edge's endpoints, so measure the gap and pass
|
|
976
|
+
a little more. A boundary loop of fewer than three edges — a crack, not a hole — has no fan to
|
|
977
|
+
build at all and is reported as left open; only a coarser weld closes that.
|
|
978
|
+
|
|
979
|
+
Not a substitute for a generator that emits a closed mesh: repair costs a full pass over the
|
|
980
|
+
geometry every render. Prompted by BelfrySCAD #521, where BOSL2's `isosurface()` computed a shared
|
|
981
|
+
voxel corner once per voxel and let the results drift 2e-5 apart.
|
|
982
|
+
|
|
949
983
|
- `src/builtins/dxf_dim.cpp` — `dxf_dim()`/`dxf_cross()`, which read a measurement out of a DXF file
|
|
950
984
|
rather than out of the model. Ported from the reference's `io/dxfdim.cc` plus the DIMENSION/LINE
|
|
951
985
|
half of `io/DxfData.cc`: `dxf_dim()` returns the first matching DIMENSION's value by its type
|
|
@@ -1653,6 +1687,20 @@ the former is worth dragging.
|
|
|
1653
1687
|
A cache hit restamps to the chain reaching the geometry *now*, not the one that
|
|
1654
1688
|
first produced it: a module called twice genuinely is two call chains.
|
|
1655
1689
|
|
|
1690
|
+
**A 2D shape is picked by `ColoredBody::sectionId`.** A `CrossSection` is contours
|
|
1691
|
+
with no run IDs, so a top-level 2D shape had no `idToNode` entry at all: its preview
|
|
1692
|
+
slab got fresh IDs from `Manifold::Extrude` in `toRenderableBodies`, after the table
|
|
1693
|
+
was built, and a click on it mapped to no source (it blocked BelfrySCAD's extrude
|
|
1694
|
+
gizmo, which exists to act on exactly those). Now the generate loop calls
|
|
1695
|
+
`tagSections` after every builtin's `GenerateFn`: any 2D body without an ID gets one
|
|
1696
|
+
reserved from Manifold's counter, attributed to that node. A transform keeps the ID
|
|
1697
|
+
it was given (it edits the body in place), so a 2D shape stays its primitive, as a
|
|
1698
|
+
solid's runs do; an op that builds a *new* section -- a 2D `union()`, `offset()`,
|
|
1699
|
+
`hull()` -- claims it, so the whole union is one pick. `restampCachedIds` restamps it
|
|
1700
|
+
by the solid rule (across renders, to the reused node), `recordRunColors` records its
|
|
1701
|
+
colour, and `toRenderableBodies` rebuilds the slab from a `MeshGL` whose one run
|
|
1702
|
+
carries it. Tested in `tests/test_python_bindings.py` (`_picked_text`).
|
|
1703
|
+
|
|
1656
1704
|
No stored script path is needed to tell the user's file from a library:
|
|
1657
1705
|
`callStack_.front()` is by construction the call made from top level.
|
|
1658
1706
|
|
|
@@ -1661,7 +1709,7 @@ Exposed to Python as `node.call_sites` (innermost-first `_CallFrame`s, each a
|
|
|
1661
1709
|
|
|
1662
1710
|
`Evaluator::measuring_` is set for the whole generate. It suppresses the four
|
|
1663
1711
|
writes that exist solely to describe *drawn* geometry — `idToNode`/`idToColor`/`idToCallChain` in
|
|
1664
|
-
`tagGenerated` and `
|
|
1712
|
+
`tagGenerated`, `tagDisplayOnly` and `tagSections`, the `restampCachedIds` call on a cache hit,
|
|
1665
1713
|
and `cacheProducer_` — because those tables are cleared once per pass, so a leak
|
|
1666
1714
|
is permanent and surfaces later as wrong click-to-source. It also suppresses
|
|
1667
1715
|
`checkDebug`, which would otherwise inject stops at the paused statement's own
|
|
@@ -42,6 +42,15 @@ struct ColoredBody {
|
|
|
42
42
|
std::optional<manifold::CrossSection> section;
|
|
43
43
|
bool flatPreview = false;
|
|
44
44
|
|
|
45
|
+
// The originalID a 2D `section` is picked by. A CrossSection is contours
|
|
46
|
+
// and carries no run IDs, so a 2D shape had no entry in idToNode and a
|
|
47
|
+
// click on its preview slab mapped to no source at all. Reserved from
|
|
48
|
+
// Manifold's own counter (so it cannot collide with a solid's ID) by the
|
|
49
|
+
// generate loop, for the node that built the section -- a primitive, or
|
|
50
|
+
// an op like union() that made a new one; a transform keeps the one it
|
|
51
|
+
// was given. toRenderableBodies() gives the slab this ID.
|
|
52
|
+
std::optional<uint32_t> sectionId;
|
|
53
|
+
|
|
45
54
|
// What a 2D `section` has been transformed BY that a CrossSection
|
|
46
55
|
// cannot itself hold: a translation along Z, a rotation out of the XY
|
|
47
56
|
// plane, anything with a Z component. Applied when
|
|
@@ -283,6 +283,9 @@ public:
|
|
|
283
283
|
// attributes there).
|
|
284
284
|
// Give cached bodies fresh originalIDs so a second call site reusing
|
|
285
285
|
// them is not confused with the first. See its definition.
|
|
286
|
+
// Give every 2D body in `bodies` that has no sectionId yet one of its
|
|
287
|
+
// own, attributed to `node`. See ColoredBody::sectionId.
|
|
288
|
+
void tagSections(std::vector<ColoredBody>& bodies, const oscad::ASTNode& node, uint32_t callChain);
|
|
286
289
|
void restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::ASTNode& node,
|
|
287
290
|
const oscad::ASTNode* producer, uint32_t callChain);
|
|
288
291
|
|
|
@@ -82,6 +82,15 @@ struct MeshRepairReport {
|
|
|
82
82
|
std::string summary() const;
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
+
//: Default welding tolerance: OpenSCAD's own GRID_FINE, 2^-20. Theirs is
|
|
86
|
+
//: the constant their Grid.h uses to decide two points are one, so a mesh
|
|
87
|
+
//: that welds there welds here. Exact in binary, so the multiply-and-round
|
|
88
|
+
//: in weldMap() adds no error of its own. Deliberately tight: welding
|
|
89
|
+
//: DISCARDS vertices, and a default that quietly merged real detail would
|
|
90
|
+
//: be worse than one that leaves a mesh open -- hole filling closes what
|
|
91
|
+
//: welding declines to. See issue #190.
|
|
92
|
+
constexpr double kDefaultWeldTolerance = 0.00000095367431640625;
|
|
93
|
+
|
|
85
94
|
// Best-effort repair, in the only order that works: welding first (it
|
|
86
95
|
// changes which edges are shared, so every later test depends on it), then
|
|
87
96
|
// degenerate and duplicate removal, then orientation, then hole filling
|
|
@@ -91,9 +100,10 @@ struct MeshRepairReport {
|
|
|
91
100
|
// Returns the repaired mesh. A mesh it cannot close is still returned,
|
|
92
101
|
// improved as far as it got, with the remainder reported in `report`.
|
|
93
102
|
template <typename M>
|
|
94
|
-
M repairMesh(const M& mesh, MeshRepairReport& report
|
|
95
|
-
|
|
96
|
-
extern template manifold::
|
|
103
|
+
M repairMesh(const M& mesh, MeshRepairReport& report,
|
|
104
|
+
double tolerance = kDefaultWeldTolerance);
|
|
105
|
+
extern template manifold::MeshGL repairMesh<manifold::MeshGL>(const manifold::MeshGL&, MeshRepairReport&, double);
|
|
106
|
+
extern template manifold::MeshGL64 repairMesh<manifold::MeshGL64>(const manifold::MeshGL64&, MeshRepairReport&, double);
|
|
97
107
|
|
|
98
108
|
struct SliverStripReport {
|
|
99
109
|
size_t removed = 0; // zero-area faces taken out
|
|
@@ -188,6 +188,10 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
|
|
|
188
188
|
const std::vector<std::unique_ptr<CSGNode>>& children,
|
|
189
189
|
const oscad::ASTNode& node);
|
|
190
190
|
|
|
191
|
+
CSGParams resolveMeshRepair(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
|
|
192
|
+
std::vector<ColoredBody> generateMeshRepair(Evaluator& ev, const CSGParams& params,
|
|
193
|
+
const std::vector<std::unique_ptr<CSGNode>>& children,
|
|
194
|
+
const oscad::ASTNode& node);
|
|
191
195
|
CSGParams resolveSimplify(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
|
|
192
196
|
std::vector<ColoredBody> generateSimplify(Evaluator& ev, const CSGParams& params,
|
|
193
197
|
const std::vector<std::unique_ptr<CSGNode>>& children,
|
{openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/function_builtins.cpp
RENAMED
|
@@ -1011,6 +1011,7 @@ const std::unordered_map<std::string, double>& featureLevels() {
|
|
|
1011
1011
|
{"sphere-styles", 1.0}, // sphere(style=)
|
|
1012
1012
|
{"export-name", 1.0}, // $export_name
|
|
1013
1013
|
{"simplify-op", 1.0}, // simplify()
|
|
1014
|
+
{"mesh-repair", 1.0}, // mesh_repair(), and import(tolerance=)
|
|
1014
1015
|
{"expr-import", 1.0}, // import() in expression position
|
|
1015
1016
|
{"object-function", 1.0}, // object(), unconditional here
|
|
1016
1017
|
{"roof-op", 1.0}, // roof(), method="voronoi" only
|
|
@@ -186,6 +186,10 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
|
|
|
186
186
|
// Not an OpenSCAD parameter. A script using it will not run upstream,
|
|
187
187
|
// which is why it has to be asked for rather than being the default.
|
|
188
188
|
const bool repair = truthy(getArg(args, std::nullopt, "repair", Value{false}));
|
|
189
|
+
// How far apart two vertices may be and still be welded into one.
|
|
190
|
+
// Named rather than hidden so import() and mesh_repair() answer the
|
|
191
|
+
// same question the same way (issue #190); undef keeps the default.
|
|
192
|
+
const Value toleranceArg = getArg(args, std::nullopt, "tolerance", Value{});
|
|
189
193
|
|
|
190
194
|
CSGParams params;
|
|
191
195
|
params["color"] = colorToValue(effCtx.color);
|
|
@@ -255,6 +259,7 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
|
|
|
255
259
|
}
|
|
256
260
|
params["kind"] = Value{std::string("mesh")};
|
|
257
261
|
params["repair"] = Value{repair};
|
|
262
|
+
params["tolerance"] = toleranceArg;
|
|
258
263
|
params["verts"] = Value{std::make_shared<const ValueList>(ValueList{std::move(vertsFlat)})};
|
|
259
264
|
params["tris"] = Value{std::make_shared<const ValueList>(ValueList{std::move(trisFlat)})};
|
|
260
265
|
return params;
|
|
@@ -315,9 +320,23 @@ std::vector<ColoredBody> generateImport(Evaluator& ev, const CSGParams& params,
|
|
|
315
320
|
const auto repairIt = params.find("repair");
|
|
316
321
|
const bool repair = repairIt != params.end() && truthy(repairIt->second);
|
|
317
322
|
if (repair) {
|
|
323
|
+
double tolerance = kDefaultWeldTolerance;
|
|
324
|
+
const auto tolIt = params.find("tolerance");
|
|
325
|
+
if (tolIt != params.end()) {
|
|
326
|
+
if (const double* t = std::get_if<double>(&tolIt->second)) {
|
|
327
|
+
if (*t < 0.0) {
|
|
328
|
+
ev.warn("import: tolerance must not be negative; using the default",
|
|
329
|
+
&node.position());
|
|
330
|
+
} else {
|
|
331
|
+
tolerance = *t;
|
|
332
|
+
}
|
|
333
|
+
} else if (!std::holds_alternative<std::monostate>(tolIt->second)) {
|
|
334
|
+
ev.warn("import: tolerance must be a number", &node.position());
|
|
335
|
+
}
|
|
336
|
+
}
|
|
318
337
|
const MeshDiagnosis before = checkMesh(mesh);
|
|
319
338
|
MeshRepairReport rep;
|
|
320
|
-
manifold::MeshGL64 fixed = repairMesh(mesh, rep);
|
|
339
|
+
manifold::MeshGL64 fixed = repairMesh(mesh, rep, tolerance);
|
|
321
340
|
const MeshDiagnosis after = checkMesh(fixed);
|
|
322
341
|
if (rep.didAnything()) {
|
|
323
342
|
ev.warn("import: repaired the mesh -- " + rep.summary(), &node.position());
|
|
@@ -344,7 +363,9 @@ std::vector<ColoredBody> generateImport(Evaluator& ev, const CSGParams& params,
|
|
|
344
363
|
if (why.empty()) why = manifoldErrorName(body.Status());
|
|
345
364
|
if (!ev.insideHull) {
|
|
346
365
|
ev.warn("import: mesh is not a closed solid (" + why +
|
|
347
|
-
|
|
366
|
+
// Same rewording as polyhedron's: see the comment there.
|
|
367
|
+
"); drawing the object as an open surface rather than a solid "
|
|
368
|
+
"-- nothing is patched. hull() can still use its points, but "
|
|
348
369
|
"it cannot take part in union/difference/intersection" +
|
|
349
370
|
std::string(repair ? "" : ". Try import(..., repair=true)"),
|
|
350
371
|
&node.position());
|
{openscad_cpp_evaluator-1.24.0 → openscad_cpp_evaluator-1.26.0}/src/builtins/primitives_3d.cpp
RENAMED
|
@@ -854,7 +854,15 @@ std::vector<ColoredBody> generatePolyhedron(Evaluator& ev, const CSGParams& para
|
|
|
854
854
|
open.first ? ", first at " + describeEdge(mesh, *open.first) : std::string();
|
|
855
855
|
ev.warn("polyhedron: mesh is not closed -- " + std::to_string(open.count) +
|
|
856
856
|
" boundary edge(s)" + where +
|
|
857
|
-
"
|
|
857
|
+
// "drawing it as a surface" was read by a reporter as
|
|
858
|
+
// the HOLE being surfaced over -- i.e. as automatic
|
|
859
|
+
// repair hiding their defect. Nothing is repaired: the
|
|
860
|
+
// mesh is drawn as the open surface it is. Name the
|
|
861
|
+
// object as the thing being drawn, and say what it is
|
|
862
|
+
// NOT, since "surface" alone does not imply "not solid"
|
|
863
|
+
// to someone who did not know it could be either.
|
|
864
|
+
"; drawing the object as an open surface rather than a "
|
|
865
|
+
"solid -- nothing is patched. hull() can still "
|
|
858
866
|
"use its points, but it cannot take part in union/difference/intersection",
|
|
859
867
|
&node.position());
|
|
860
868
|
}
|
|
@@ -37,6 +37,7 @@ const std::unordered_map<std::string_view, ResolveFn>& resolveDispatch() {
|
|
|
37
37
|
{"fill", &resolveFill},
|
|
38
38
|
{"minkowski_difference", &resolveMinkowskiDifference},
|
|
39
39
|
{"simplify", &resolveSimplify},
|
|
40
|
+
{"mesh_repair", &resolveMeshRepair},
|
|
40
41
|
{"levelset", &resolveLevelSet},
|
|
41
42
|
{"offset", &resolveOffset},
|
|
42
43
|
{"surface", &resolveSurface},
|
|
@@ -79,6 +80,7 @@ const std::unordered_map<std::string_view, GenerateFn>& generateDispatch() {
|
|
|
79
80
|
{"fill", &generateFill},
|
|
80
81
|
{"minkowski_difference", &generateMinkowskiDifference},
|
|
81
82
|
{"simplify", &generateSimplify},
|
|
83
|
+
{"mesh_repair", &generateMeshRepair},
|
|
82
84
|
{"levelset", &generateLevelSet},
|
|
83
85
|
{"offset", &generateOffset},
|
|
84
86
|
{"surface", &generateSurface},
|
|
@@ -122,7 +124,7 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
|
|
|
122
124
|
// full and quietly given the whole drawing.)
|
|
123
125
|
{"import",
|
|
124
126
|
{"file", "layer", "convexity", "origin", "scale", "width", "height", "filename", "layername", "center", "dpi",
|
|
125
|
-
"id", "class", "repair"}},
|
|
127
|
+
"id", "class", "repair", "tolerance"}},
|
|
126
128
|
{"linear_extrude", {"height", "v", "scale", "center", "twist", "slices", "segments", "convexity"}},
|
|
127
129
|
{"rotate_extrude", {"angle", "start", "convexity"}},
|
|
128
130
|
{"projection", {"cut", "convexity"}},
|
|
@@ -130,6 +132,7 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
|
|
|
130
132
|
{"fill", {}},
|
|
131
133
|
{"minkowski_difference", {}},
|
|
132
134
|
{"simplify", {"tolerance"}},
|
|
135
|
+
{"mesh_repair", {"tolerance"}},
|
|
133
136
|
{"levelset", {"field", "bounds", "isovalue", "invert", "edge"}},
|
|
134
137
|
{"offset", {"r", "delta", "chamfer"}},
|
|
135
138
|
{"surface", {"file", "center", "convexity", "invert"}},
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include "openscad_cpp_evaluator/call_args.hpp"
|
|
4
4
|
#include "openscad_cpp_evaluator/evaluator.hpp"
|
|
5
|
+
#include "openscad_cpp_evaluator/mesh_check.hpp"
|
|
5
6
|
|
|
6
7
|
#include <manifold/polygon.h>
|
|
7
8
|
|
|
@@ -503,6 +504,117 @@ std::vector<ColoredBody> generateSimplify(Evaluator& ev, const CSGParams& params
|
|
|
503
504
|
return out;
|
|
504
505
|
}
|
|
505
506
|
|
|
507
|
+
// mesh_repair(tolerance) -- make a broken mesh watertight. A BelfrySCAD
|
|
508
|
+
// extension: nothing in the OpenSCAD language can repair geometry, and a
|
|
509
|
+
// mesh arriving from a library generator or an import is routinely almost
|
|
510
|
+
// closed rather than closed. See issue #190.
|
|
511
|
+
//
|
|
512
|
+
// The work is repairMesh()'s, already used by import(..., repair=true);
|
|
513
|
+
// this exposes the same six steps to any geometry, not only imported
|
|
514
|
+
// files: weld, drop degenerate/duplicate, orient, fill holes, flip
|
|
515
|
+
// outward, strip zero-area faces.
|
|
516
|
+
//
|
|
517
|
+
// NOT a substitute for a generator that emits a closed mesh. Repair costs
|
|
518
|
+
// a full pass over the geometry every render, and welding discards
|
|
519
|
+
// vertices -- which is why the default tolerance is tight enough to
|
|
520
|
+
// weld only what is unambiguously one point, leaving hole filling to
|
|
521
|
+
// close the rest.
|
|
522
|
+
|
|
523
|
+
CSGParams resolveMeshRepair(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
|
|
524
|
+
auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
|
|
525
|
+
CSGParams params;
|
|
526
|
+
// Position 0, so mesh_repair(0.001) reads the way simplify(0.1) does.
|
|
527
|
+
params["tolerance"] = getArg(args, 0, "tolerance", Value{});
|
|
528
|
+
EvalContext blockCtx = ev.blockScope(effCtx);
|
|
529
|
+
ev.evalChildren(node.children, blockCtx);
|
|
530
|
+
return params;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
std::vector<ColoredBody> generateMeshRepair(Evaluator& ev, const CSGParams& params,
|
|
534
|
+
const std::vector<std::unique_ptr<CSGNode>>& children,
|
|
535
|
+
const oscad::ASTNode& node) {
|
|
536
|
+
const std::vector<ColoredBody> bodies = flattenCsgTree(children);
|
|
537
|
+
if (bodies.empty()) return {};
|
|
538
|
+
|
|
539
|
+
const Value tolArg = params.at("tolerance");
|
|
540
|
+
double tolerance = kDefaultWeldTolerance;
|
|
541
|
+
if (std::holds_alternative<double>(tolArg)) {
|
|
542
|
+
tolerance = std::get<double>(tolArg);
|
|
543
|
+
if (tolerance < 0.0) {
|
|
544
|
+
ev.warn("mesh_repair: tolerance must not be negative", &node.position());
|
|
545
|
+
return bodies;
|
|
546
|
+
}
|
|
547
|
+
} else if (!std::holds_alternative<std::monostate>(tolArg)) {
|
|
548
|
+
ev.warn("mesh_repair: tolerance must be a number", &node.position());
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
std::vector<ColoredBody> out;
|
|
552
|
+
out.reserve(bodies.size());
|
|
553
|
+
for (const ColoredBody& b : bodies) {
|
|
554
|
+
// Background/highlight geometry is a debugging aid, not part of the
|
|
555
|
+
// model; repairing it would spend the pass and say nothing useful.
|
|
556
|
+
// A 2D section has no mesh to repair either.
|
|
557
|
+
if (b.role != BodyRole::Normal || (!b.body && !b.rawMesh)) {
|
|
558
|
+
out.push_back(b);
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
// The case this module exists for: polyhedron() could not build a
|
|
562
|
+
// Manifold from an open mesh, so it kept the raw soup in rawMesh
|
|
563
|
+
// and left `body` set but EMPTY (see ColoredBody::rawMesh). Repair
|
|
564
|
+
// the soup -- repairing the empty Manifold would find nothing to
|
|
565
|
+
// do and report success at having done nothing.
|
|
566
|
+
MeshRepairReport report;
|
|
567
|
+
ColoredBody cb = b;
|
|
568
|
+
manifold::MeshGL64 fixed;
|
|
569
|
+
if (b.rawMesh) {
|
|
570
|
+
manifold::MeshGL64 soup;
|
|
571
|
+
soup.numProp = b.rawMesh->numProp;
|
|
572
|
+
soup.vertProperties.assign(b.rawMesh->vertProperties.begin(),
|
|
573
|
+
b.rawMesh->vertProperties.end());
|
|
574
|
+
soup.triVerts.assign(b.rawMesh->triVerts.begin(), b.rawMesh->triVerts.end());
|
|
575
|
+
fixed = repairMesh(soup, report, tolerance);
|
|
576
|
+
} else {
|
|
577
|
+
fixed = repairMesh(b.body->GetMeshGL64(), report, tolerance);
|
|
578
|
+
}
|
|
579
|
+
if (report.didAnything()) {
|
|
580
|
+
manifold::Manifold rebuilt(fixed);
|
|
581
|
+
if (rebuilt.Status() == manifold::Manifold::Error::NoError) {
|
|
582
|
+
cb.body = std::move(rebuilt);
|
|
583
|
+
// It is a real solid now, so stop carrying the display-only
|
|
584
|
+
// soup: leaving it set would keep splitByRole() pulling the
|
|
585
|
+
// body aside as un-CSG-able, which is the whole thing the
|
|
586
|
+
// repair just fixed.
|
|
587
|
+
cb.rawMesh.reset();
|
|
588
|
+
// triColors is indexed by triangle, and repair changes how
|
|
589
|
+
// many there are -- filling adds, sliver stripping removes.
|
|
590
|
+
// Carrying it across would mask an N-triangle mesh with an
|
|
591
|
+
// M-entry array, which is an IndexError rather than a wrong
|
|
592
|
+
// colour. Same reasoning as simplify()'s.
|
|
593
|
+
cb.triColors.reset();
|
|
594
|
+
ev.warn("mesh_repair: " + report.summary(), &node.position());
|
|
595
|
+
} else {
|
|
596
|
+
// Say what it could not do. Silently returning the original
|
|
597
|
+
// would leave the author believing a repair happened.
|
|
598
|
+
ev.warn("mesh_repair: could not rebuild the repaired mesh (" +
|
|
599
|
+
std::string(manifoldErrorName(rebuilt.Status())) +
|
|
600
|
+
"); geometry left unchanged",
|
|
601
|
+
&node.position());
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (report.unfilledHoles) {
|
|
605
|
+
// The sub-3-edge boundary loop: a crack between two nearly
|
|
606
|
+
// coincident vertices, with no fan to build. Only a coarser
|
|
607
|
+
// weld closes it, so name the knob rather than just the count.
|
|
608
|
+
ev.warn("mesh_repair: " + std::to_string(report.unfilledHoles) +
|
|
609
|
+
" hole(s) left open -- a crack too narrow to fill needs a"
|
|
610
|
+
" larger tolerance to weld shut instead",
|
|
611
|
+
&node.position());
|
|
612
|
+
}
|
|
613
|
+
out.push_back(std::move(cb));
|
|
614
|
+
}
|
|
615
|
+
return out;
|
|
616
|
+
}
|
|
617
|
+
|
|
506
618
|
// -- levelset(field, bounds, isovalue) -------------------------------------
|
|
507
619
|
//
|
|
508
620
|
// A solid from a grid of sampled values. Wraps Manifold::LevelSet with a C++
|
|
@@ -37,6 +37,18 @@ std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodi
|
|
|
37
37
|
if (!cb.body && cb.section) {
|
|
38
38
|
ColoredBody flat;
|
|
39
39
|
flat.body = manifold::Manifold::Extrude(cb.section->ToPolygons(), kTopLevel2dHeight);
|
|
40
|
+
if (cb.sectionId && !flat.body->IsEmpty()) {
|
|
41
|
+
// One run carrying the section's own ID, so a click on the
|
|
42
|
+
// slab finds the node that built the shape (see
|
|
43
|
+
// ColoredBody::sectionId). Manifold keeps a MeshGL's
|
|
44
|
+
// runOriginalID when it is given one.
|
|
45
|
+
manifold::MeshGL mesh = flat.body->GetMeshGL();
|
|
46
|
+
mesh.runOriginalID = {*cb.sectionId};
|
|
47
|
+
mesh.runIndex = {0, static_cast<uint32_t>(mesh.triVerts.size())};
|
|
48
|
+
mesh.runTransform.clear();
|
|
49
|
+
mesh.faceID.clear();
|
|
50
|
+
flat.body = manifold::Manifold(mesh);
|
|
51
|
+
}
|
|
40
52
|
// Put it back where the transforms a CrossSection could not
|
|
41
53
|
// hold would have placed it -- a Z offset, a rotation out of
|
|
42
54
|
// the plane. See ColoredBody::sectionXform.
|
|
@@ -124,6 +124,7 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
|
|
|
124
124
|
generateWarnEntry = node.warnEntry;
|
|
125
125
|
generateCallChain = node.callChain;
|
|
126
126
|
node.bodies = it->second(*this, node.params, node.children, *node.node);
|
|
127
|
+
if (!measuring_) tagSections(node.bodies, *node.node, node.callChain);
|
|
127
128
|
generateWarnEntry = savedWarnEntry;
|
|
128
129
|
generateCallChain = savedCallChain;
|
|
129
130
|
} else {
|
|
@@ -393,6 +394,16 @@ void Evaluator::restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::
|
|
|
393
394
|
// its producer check keeps the first copy's descendants. They occupy
|
|
394
395
|
// the same space, so there is nothing to select apart.
|
|
395
396
|
for (ColoredBody& cb : bodies) {
|
|
397
|
+
// A 2D body's one ID, restamped by the same rule as a solid's runs.
|
|
398
|
+
if (cb.sectionId) {
|
|
399
|
+
const uint32_t fresh = manifold::Manifold::ReserveIDs(1);
|
|
400
|
+
auto old = idToNode.find(*cb.sectionId);
|
|
401
|
+
idToNode[fresh] =
|
|
402
|
+
(old != idToNode.end() && old->second != producer) ? old->second : &node;
|
|
403
|
+
idToCallChain[fresh] = callChain;
|
|
404
|
+
idToColor[fresh] = cb.color;
|
|
405
|
+
cb.sectionId = fresh;
|
|
406
|
+
}
|
|
396
407
|
if (!cb.body || cb.body->IsEmpty()) continue;
|
|
397
408
|
manifold::MeshGL mesh = cb.body->GetMeshGL();
|
|
398
409
|
if (mesh.runOriginalID.empty()) continue;
|
|
@@ -475,6 +486,7 @@ void Evaluator::restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::
|
|
|
475
486
|
|
|
476
487
|
|
|
477
488
|
void Evaluator::recordRunColors(ColoredBody& b, const std::optional<std::array<float, 4>>& rgba) {
|
|
489
|
+
if (b.sectionId) idToColor[*b.sectionId] = rgba;
|
|
478
490
|
if (!b.body || bodyIsEmpty(b)) return;
|
|
479
491
|
// A body that is still one original knows its own ID without building
|
|
480
492
|
// a mesh -- the common case, and the cheap one.
|
|
@@ -486,6 +498,18 @@ void Evaluator::recordRunColors(ColoredBody& b, const std::optional<std::array<f
|
|
|
486
498
|
for (uint32_t id : b.body->GetMeshGL().runOriginalID) idToColor[id] = rgba;
|
|
487
499
|
}
|
|
488
500
|
|
|
501
|
+
void Evaluator::tagSections(std::vector<ColoredBody>& bodies, const oscad::ASTNode& node,
|
|
502
|
+
uint32_t callChain) {
|
|
503
|
+
for (ColoredBody& cb : bodies) {
|
|
504
|
+
if (!cb.section || cb.body || cb.sectionId) continue;
|
|
505
|
+
const uint32_t id = manifold::Manifold::ReserveIDs(1);
|
|
506
|
+
idToNode[id] = &node;
|
|
507
|
+
idToColor[id] = cb.color;
|
|
508
|
+
idToCallChain[id] = callChain;
|
|
509
|
+
cb.sectionId = id;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
489
513
|
ColoredBody Evaluator::tagGenerated(manifold::Manifold body, const oscad::ASTNode& node, const Value& colorValue) {
|
|
490
514
|
manifold::MeshGL mesh = body.GetMeshGL();
|
|
491
515
|
std::optional<std::array<float, 4>> color = valueToColor(colorValue);
|
|
@@ -309,7 +309,7 @@ namespace {
|
|
|
309
309
|
// this: unwelded duplicates split one shared edge into two boundary edges,
|
|
310
310
|
// so a mesh that is really closed reads as full of holes until it is done.
|
|
311
311
|
template <typename M>
|
|
312
|
-
std::vector<Vert> weldMap(const M& m, size_t& welded) {
|
|
312
|
+
std::vector<Vert> weldMap(const M& m, size_t& welded, double tolerance) {
|
|
313
313
|
const size_t stride = m.numProp ? m.numProp : 3;
|
|
314
314
|
const size_t n = stride ? m.vertProperties.size() / stride : 0;
|
|
315
315
|
std::vector<Vert> remap(n);
|
|
@@ -332,12 +332,17 @@ std::vector<Vert> weldMap(const M& m, size_t& welded) {
|
|
|
332
332
|
}
|
|
333
333
|
bool samePos(const Keyed& o) const { return x == o.x && y == o.y && z == o.z; }
|
|
334
334
|
};
|
|
335
|
+
// Snap to a grid of `tolerance`. Guarded because a caller can pass 0 or
|
|
336
|
+
// a negative to mean "weld only exact duplicates" -- dividing by that
|
|
337
|
+
// would give inf/NaN keys and weld the whole mesh into one point.
|
|
338
|
+
const double inv = tolerance > 0.0 ? 1.0 / tolerance : 0.0;
|
|
335
339
|
std::vector<Keyed> keyed;
|
|
336
340
|
keyed.reserve(n);
|
|
337
341
|
for (size_t v = 0; v < n; ++v) {
|
|
338
342
|
double p[3];
|
|
339
343
|
pos(m, static_cast<Vert>(v), p);
|
|
340
|
-
keyed.push_back({llround(p[0] *
|
|
344
|
+
keyed.push_back({llround(p[0] * inv), llround(p[1] * inv), llround(p[2] * inv),
|
|
345
|
+
static_cast<Vert>(v)});
|
|
341
346
|
}
|
|
342
347
|
std::sort(keyed.begin(), keyed.end());
|
|
343
348
|
for (size_t i = 0; i < keyed.size();) {
|
|
@@ -400,14 +405,14 @@ std::vector<std::vector<Vert>> boundaryLoops(const std::vector<std::array<Vert,
|
|
|
400
405
|
} // namespace
|
|
401
406
|
|
|
402
407
|
template <typename M>
|
|
403
|
-
M repairMesh(const M& mesh, MeshRepairReport& report) {
|
|
408
|
+
M repairMesh(const M& mesh, MeshRepairReport& report, double tolerance) {
|
|
404
409
|
report = MeshRepairReport{};
|
|
405
410
|
M out = mesh;
|
|
406
411
|
|
|
407
412
|
// 1. Weld. Must come first -- it decides which edges are shared, and
|
|
408
413
|
// every later step reads the edge map.
|
|
409
414
|
size_t welded = 0;
|
|
410
|
-
const std::vector<Vert> remap = weldMap(mesh, welded);
|
|
415
|
+
const std::vector<Vert> remap = weldMap(mesh, welded, tolerance);
|
|
411
416
|
report.weldedVertices = welded;
|
|
412
417
|
|
|
413
418
|
// Remap every face, then find the duplicates by sorting rather than by
|
|
@@ -850,8 +855,8 @@ M stripSlivers(const M& mesh, SliverStripReport& report) {
|
|
|
850
855
|
// header, keeps compile times where they were.
|
|
851
856
|
template MeshDiagnosis checkMesh<manifold::MeshGL>(const manifold::MeshGL&);
|
|
852
857
|
template MeshDiagnosis checkMesh<manifold::MeshGL64>(const manifold::MeshGL64&);
|
|
853
|
-
template manifold::MeshGL repairMesh<manifold::MeshGL>(const manifold::MeshGL&, MeshRepairReport
|
|
854
|
-
template manifold::MeshGL64 repairMesh<manifold::MeshGL64>(const manifold::MeshGL64&, MeshRepairReport
|
|
858
|
+
template manifold::MeshGL repairMesh<manifold::MeshGL>(const manifold::MeshGL&, MeshRepairReport&, double);
|
|
859
|
+
template manifold::MeshGL64 repairMesh<manifold::MeshGL64>(const manifold::MeshGL64&, MeshRepairReport&, double);
|
|
855
860
|
template manifold::MeshGL stripSlivers<manifold::MeshGL>(const manifold::MeshGL&, SliverStripReport&);
|
|
856
861
|
template manifold::MeshGL64 stripSlivers<manifold::MeshGL64>(const manifold::MeshGL64&, SliverStripReport&);
|
|
857
862
|
|
|
@@ -1660,3 +1660,99 @@ TEST(CsgTree, InnerNodeBodiesAreReleasedOnceTheParentHasConsumedThem) {
|
|
|
1660
1660
|
Evaluated plain = evalSrc("union() { cube(1); sphere(1); }"); // the union really was consumed first
|
|
1661
1661
|
EXPECT_NEAR(e.bodies[0].body->Volume(), plain.bodies[0].body->Volume(), 1e-9);
|
|
1662
1662
|
}
|
|
1663
|
+
|
|
1664
|
+
// -- mesh_repair() ----------------------------------------------------------
|
|
1665
|
+
//
|
|
1666
|
+
// Issue #190, out of BelfrySCAD #521: a mesh that is almost closed is not a
|
|
1667
|
+
// solid, and nothing in the language could make it one. The repair itself is
|
|
1668
|
+
// repairMesh()'s, already covered in test_mesh_check.cpp; these cover the
|
|
1669
|
+
// module -- that it reaches the raw soup polyhedron() keeps, that the result
|
|
1670
|
+
// is a real solid, and that tolerance does what it claims.
|
|
1671
|
+
|
|
1672
|
+
namespace {
|
|
1673
|
+
// A 20-cube with its lid left off: five faces, four boundary edges.
|
|
1674
|
+
const char* kOpenCube =
|
|
1675
|
+
"polyhedron(points=[[-10,-10,-10],[10,-10,-10],[10,10,-10],[-10,10,-10],"
|
|
1676
|
+
" [-10,-10, 10],[10,-10, 10],[10,10, 10],[-10,10, 10]],"
|
|
1677
|
+
" faces=[[0,1,2,3],[4,7,6,5],[0,4,5,1],[1,5,6,2],[2,6,7,3]]);";
|
|
1678
|
+
} // namespace
|
|
1679
|
+
|
|
1680
|
+
TEST(MeshRepairModule, TurnsAnOpenMeshIntoASolid) {
|
|
1681
|
+
// Unrepaired, polyhedron() cannot build a Manifold at all: it keeps the
|
|
1682
|
+
// triangle soup for display and leaves an EMPTY body behind.
|
|
1683
|
+
Evaluated broken = evalSrc(kOpenCube);
|
|
1684
|
+
ASSERT_EQ(broken.bodies.size(), 1u);
|
|
1685
|
+
EXPECT_TRUE(broken.bodies[0].rawMesh.has_value());
|
|
1686
|
+
EXPECT_DOUBLE_EQ(broken.bodies[0].body->Volume(), 0.0);
|
|
1687
|
+
|
|
1688
|
+
Evaluated fixed = evalSrc(std::string("mesh_repair() ") + kOpenCube);
|
|
1689
|
+
ASSERT_EQ(fixed.bodies.size(), 1u);
|
|
1690
|
+
// The soup is gone: splitByRole() would otherwise keep pulling this
|
|
1691
|
+
// aside as un-CSG-able, which is the thing the repair just fixed.
|
|
1692
|
+
EXPECT_FALSE(fixed.bodies[0].rawMesh.has_value());
|
|
1693
|
+
EXPECT_NEAR(fixed.bodies[0].body->Volume(), 20.0 * 20 * 20, 1e-6);
|
|
1694
|
+
EXPECT_EQ(fixed.bodies[0].body->Status(), manifold::Manifold::Error::NoError);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
TEST(MeshRepairModule, TheResultCanTakePartInCSG) {
|
|
1698
|
+
// The whole point. An unrepaired open mesh is display-only.
|
|
1699
|
+
Evaluated cut = evalSrc(std::string("difference() { mesh_repair() ") + kOpenCube +
|
|
1700
|
+
" cube(10, center=true); }");
|
|
1701
|
+
ASSERT_EQ(cut.bodies.size(), 1u);
|
|
1702
|
+
EXPECT_NEAR(cut.bodies[0].body->Volume(), 20.0 * 20 * 20 - 10.0 * 10 * 10, 1e-6);
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
TEST(MeshRepairModule, SaysWhatItDid) {
|
|
1706
|
+
std::string said;
|
|
1707
|
+
evalSrc(std::string("mesh_repair() ") + kOpenCube,
|
|
1708
|
+
[&](const std::string& m) { if (m.find("mesh_repair") != std::string::npos && said.empty()) said = m; });
|
|
1709
|
+
EXPECT_NE(said.find("hole filled"), std::string::npos) << said;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
TEST(MeshRepairModule, LeavesAClosedMeshAloneAndSaysNothing) {
|
|
1713
|
+
const char* closed = "cube(10, center=true);";
|
|
1714
|
+
std::string said;
|
|
1715
|
+
Evaluated e = evalSrc(std::string("mesh_repair() ") + closed,
|
|
1716
|
+
[&](const std::string& m) { if (said.empty()) said = m; });
|
|
1717
|
+
EXPECT_EQ(said, "");
|
|
1718
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 1000.0, 1e-6);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
TEST(MeshRepairModule, ToleranceWeldsVerticesThatTheDefaultLeavesSplit) {
|
|
1722
|
+
// Two cubes sharing a face, with the shared corners 1e-3 apart -- far
|
|
1723
|
+
// above GRID_FINE, so the default welds nothing and only hole filling
|
|
1724
|
+
// closes it. A tolerance covering the gap welds instead, and the two
|
|
1725
|
+
// routes are distinguishable by triangle count: filling ADDS faces.
|
|
1726
|
+
const char* split =
|
|
1727
|
+
"polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0],"
|
|
1728
|
+
" [0,0,10],[10,0,10],[10,10,10],[0.001,10,10]],"
|
|
1729
|
+
" faces=[[0,1,2,3],[4,7,6,5],[0,4,5,1],[1,5,6,2],[2,6,7,3],[3,7,4,0]]);";
|
|
1730
|
+
Evaluated tight = evalSrc(std::string("mesh_repair() ") + split);
|
|
1731
|
+
Evaluated loose = evalSrc(std::string("mesh_repair(0.01) ") + split);
|
|
1732
|
+
ASSERT_EQ(tight.bodies.size(), 1u);
|
|
1733
|
+
ASSERT_EQ(loose.bodies.size(), 1u);
|
|
1734
|
+
// Both must end up solid; the looser weld should not need more geometry
|
|
1735
|
+
// than the tighter one.
|
|
1736
|
+
EXPECT_EQ(tight.bodies[0].body->Status(), manifold::Manifold::Error::NoError);
|
|
1737
|
+
EXPECT_EQ(loose.bodies[0].body->Status(), manifold::Manifold::Error::NoError);
|
|
1738
|
+
EXPECT_LE(loose.bodies[0].body->NumTri(), tight.bodies[0].body->NumTri());
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
TEST(MeshRepairModule, RejectsANegativeTolerance) {
|
|
1742
|
+
// polyhedron() warns about the open mesh first, so pick out ours.
|
|
1743
|
+
std::string said;
|
|
1744
|
+
evalSrc(std::string("mesh_repair(-1) ") + kOpenCube,
|
|
1745
|
+
[&](const std::string& m) {
|
|
1746
|
+
if (said.empty() && m.find("mesh_repair") != std::string::npos) said = m;
|
|
1747
|
+
});
|
|
1748
|
+
EXPECT_NE(said.find("must not be negative"), std::string::npos) << said;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
TEST(MeshRepairModule, IsAdvertisedBySupportedFeature) {
|
|
1752
|
+
// A portable script guards on the capability before calling the module,
|
|
1753
|
+
// since mesh_repair() is an extension and will not parse upstream.
|
|
1754
|
+
std::string said;
|
|
1755
|
+
evalSrc("echo(supported_feature(\"mesh-repair\"));",
|
|
1756
|
+
[&](const std::string& m) { if (said.empty()) said = m; });
|
|
1757
|
+
EXPECT_NE(said.find("1"), std::string::npos) << said;
|
|
1758
|
+
}
|