openscad-cpp-evaluator 0.23.0__tar.gz → 0.25.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-0.23.0 → openscad_cpp_evaluator-0.25.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/bindings/module.cpp +21 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/mesh_check.hpp +30 -3
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/python/openscad_cpp_evaluator/__init__.py +15 -1
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/mesh_check.cpp +220 -4
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_mesh_check.cpp +168 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/README.md +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/text/stb_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/uv.lock +0 -0
|
@@ -247,6 +247,24 @@ nb::list csgTreeToPy(const std::vector<std::unique_ptr<oscadeval::CSGNode>>& tre
|
|
|
247
247
|
// Exposed so a front end that writes its own files -- BelfrySCAD's GUI has
|
|
248
248
|
// its own exporters -- can run the same check the CLI does rather than a
|
|
249
249
|
// second, drifting implementation.
|
|
250
|
+
// Exposed for the export path: strip zero-area faces and repair the
|
|
251
|
+
// T-joints their removal exposes. Returns (verts, tris, report).
|
|
252
|
+
nb::tuple stripSliversPy(const std::vector<float>& verts, const std::vector<uint32_t>& tris) {
|
|
253
|
+
manifold::MeshGL m;
|
|
254
|
+
m.numProp = 3;
|
|
255
|
+
m.vertProperties = verts;
|
|
256
|
+
m.triVerts = tris;
|
|
257
|
+
oscadeval::SliverStripReport r;
|
|
258
|
+
manifold::MeshGL out = oscadeval::stripSlivers(m, r);
|
|
259
|
+
nb::dict rep;
|
|
260
|
+
rep["removed"] = r.removed;
|
|
261
|
+
rep["restitched"] = r.restitched;
|
|
262
|
+
rep["needles"] = r.needles;
|
|
263
|
+
rep["left_behind"] = r.leftBehind;
|
|
264
|
+
rep["passes"] = r.passes;
|
|
265
|
+
return nb::make_tuple(out.vertProperties, out.triVerts, rep);
|
|
266
|
+
}
|
|
267
|
+
|
|
250
268
|
nb::dict checkMeshPy(const std::vector<float>& verts, const std::vector<uint32_t>& tris) {
|
|
251
269
|
manifold::MeshGL m;
|
|
252
270
|
m.numProp = 3;
|
|
@@ -708,6 +726,9 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
|
|
|
708
726
|
"Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit).");
|
|
709
727
|
m.def("parse_decls", &parseDecls, nb::arg("path"),
|
|
710
728
|
"Parse a .scad file; return top-level declaration (namespace, name, start, end, line, column, origin) tuples.");
|
|
729
|
+
m.def("strip_slivers", &stripSliversPy, nb::arg("verts"), nb::arg("tris"),
|
|
730
|
+
"Remove zero-area faces and repair the T-joints their removal "
|
|
731
|
+
"exposes. Returns (verts, tris, report).");
|
|
711
732
|
m.def("check_mesh", &checkMeshPy, nb::arg("verts"), nb::arg("tris"),
|
|
712
733
|
"Diagnose a triangle mesh against the manifoldness conditions. "
|
|
713
734
|
"verts is a flat [x,y,z,...] list, tris a flat index list. Returns "
|
|
@@ -34,9 +34,13 @@ struct MeshDiagnosis {
|
|
|
34
34
|
size_t unweldedVertices = 0;
|
|
35
35
|
|
|
36
36
|
bool watertight() const { return boundaryEdges == 0; }
|
|
37
|
+
// Topology only. Zero-area faces are deliberately not part of this: a
|
|
38
|
+
// mesh full of slivers can be a perfectly good closed manifold, and CSG
|
|
39
|
+
// routinely emits them -- a level-4 Menger sponge has 14. They are worth
|
|
40
|
+
// reporting, because some slicers drop them and are then left with real
|
|
41
|
+
// holes, but calling such a mesh "not manifold" is simply wrong.
|
|
37
42
|
bool manifold() const {
|
|
38
|
-
return boundaryEdges == 0 && nonManifoldEdges == 0 && pinchedVertices == 0
|
|
39
|
-
&& degenerateFaces == 0;
|
|
43
|
+
return boundaryEdges == 0 && nonManifoldEdges == 0 && pinchedVertices == 0;
|
|
40
44
|
}
|
|
41
45
|
bool orientable() const { return inconsistentEdges == 0; }
|
|
42
46
|
bool ok() const { return manifold() && orientable(); }
|
|
@@ -59,10 +63,11 @@ struct MeshRepairReport {
|
|
|
59
63
|
size_t filledTriangles = 0;
|
|
60
64
|
size_t splitVertices = 0;
|
|
61
65
|
size_t unfilledHoles = 0; // boundary loops it could not close
|
|
66
|
+
size_t strippedSlivers = 0; // zero-area faces removed and restitched
|
|
62
67
|
|
|
63
68
|
bool didAnything() const {
|
|
64
69
|
return weldedVertices || droppedDegenerate || droppedDuplicate || reversedFaces
|
|
65
|
-
|| filledHoles || splitVertices;
|
|
70
|
+
|| filledHoles || splitVertices || strippedSlivers;
|
|
66
71
|
}
|
|
67
72
|
// "welded 12 vertices, filled 1 hole (4 triangles)" -- empty if nothing.
|
|
68
73
|
std::string summary() const;
|
|
@@ -78,4 +83,26 @@ struct MeshRepairReport {
|
|
|
78
83
|
// improved as far as it got, with the remainder reported in `report`.
|
|
79
84
|
manifold::MeshGL repairMesh(const manifold::MeshGL& mesh, MeshRepairReport& report);
|
|
80
85
|
|
|
86
|
+
struct SliverStripReport {
|
|
87
|
+
size_t removed = 0; // zero-area faces taken out
|
|
88
|
+
size_t restitched = 0; // neighbours split to close the gap they left
|
|
89
|
+
// Faces with two corners at one position. These need no restitching:
|
|
90
|
+
// their two long edges run between the same two points, so the faces on
|
|
91
|
+
// either side already meet once the needle is gone.
|
|
92
|
+
size_t needles = 0;
|
|
93
|
+
size_t leftBehind = 0; // slivers whose neighbour could not be found
|
|
94
|
+
size_t passes = 0; // removing one can expose another
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Remove zero-area faces and repair the T-joints their removal exposes.
|
|
98
|
+
//
|
|
99
|
+
// A sliver's three vertices are collinear, so one lies between the other
|
|
100
|
+
// two. Dropping the face leaves that middle vertex sitting on the interior
|
|
101
|
+
// of the neighbour's edge -- a T-joint -- and the two sides no longer share
|
|
102
|
+
// an edge, which reads as a hole. Splitting the neighbour at the middle
|
|
103
|
+
// vertex restores the match without moving any geometry.
|
|
104
|
+
//
|
|
105
|
+
// The mesh is returned unchanged if it has no slivers.
|
|
106
|
+
manifold::MeshGL stripSlivers(const manifold::MeshGL& mesh, SliverStripReport& report);
|
|
107
|
+
|
|
81
108
|
} // namespace oscadeval
|
|
@@ -18,7 +18,7 @@ from ._openscad_cpp_evaluator import FastContinueSignal, ManifoldCache
|
|
|
18
18
|
__all__ = [
|
|
19
19
|
"Evaluator", "ColoredBody", "EvalError", "ParseError", "OscObject", "parse", "to_renderable_bodies",
|
|
20
20
|
"ManifoldCache", "CallSiteProfile", "ProfileResult", "format_csg_tree", "bodies_from_dicts",
|
|
21
|
-
"FastContinueSignal", "parse_ast", "parse_ast_string", "check_mesh",
|
|
21
|
+
"FastContinueSignal", "parse_ast", "parse_ast_string", "check_mesh", "strip_slivers",
|
|
22
22
|
]
|
|
23
23
|
|
|
24
24
|
|
|
@@ -274,6 +274,20 @@ def parse(path: str) -> RootScope:
|
|
|
274
274
|
return RootScope(decls)
|
|
275
275
|
|
|
276
276
|
|
|
277
|
+
def strip_slivers(verts, tris):
|
|
278
|
+
"""Remove zero-area faces and repair the T-joints removal exposes.
|
|
279
|
+
|
|
280
|
+
A sliver's three vertices are collinear, so one lies between the other
|
|
281
|
+
two. Dropping the face leaves that middle vertex on the interior of the
|
|
282
|
+
neighbour's edge, and the two sides no longer share an edge -- which
|
|
283
|
+
reads as a hole. Splitting the neighbour there restores the match
|
|
284
|
+
without moving any geometry.
|
|
285
|
+
|
|
286
|
+
Returns (verts, tris, report).
|
|
287
|
+
"""
|
|
288
|
+
return _ext.strip_slivers([float(v) for v in verts], [int(t) for t in tris])
|
|
289
|
+
|
|
290
|
+
|
|
277
291
|
def check_mesh(verts, tris) -> dict:
|
|
278
292
|
"""Diagnose a triangle mesh against the manifoldness conditions.
|
|
279
293
|
|
|
@@ -29,6 +29,14 @@ void pos(const manifold::MeshGL& m, Vert v, double out[3]) {
|
|
|
29
29
|
for (int i = 0; i < 3; ++i) out[i] = m.vertProperties[v * stride + i];
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// A face naming the same vertex twice. Its "edges" include a self-edge, so
|
|
33
|
+
// it cannot take part in the topology at all.
|
|
34
|
+
bool repeatsAVertex(const manifold::MeshGL& m, size_t t) {
|
|
35
|
+
Vert v[3];
|
|
36
|
+
triVerts(m, t, v);
|
|
37
|
+
return v[0] == v[1] || v[1] == v[2] || v[0] == v[2];
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
bool degenerate(const manifold::MeshGL& m, size_t t) {
|
|
33
41
|
Vert v[3];
|
|
34
42
|
triVerts(m, t, v);
|
|
@@ -136,10 +144,16 @@ MeshDiagnosis checkMesh(const manifold::MeshGL& mesh) {
|
|
|
136
144
|
std::set<std::array<Vert, 3>> seenFaces;
|
|
137
145
|
|
|
138
146
|
for (size_t t = 0; t < tris; ++t) {
|
|
139
|
-
if (degenerate(mesh, t))
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
if (degenerate(mesh, t)) ++d.degenerateFaces;
|
|
148
|
+
// Counted, but NOT removed from the topology. A zero-area triangle
|
|
149
|
+
// is still a face with three edges, and dropping it leaves those
|
|
150
|
+
// edges with one face each -- which then reads as a hole that is
|
|
151
|
+
// not there. A level-4 Menger sponge is manifold with every face
|
|
152
|
+
// present and reported 722 boundary edges without them.
|
|
153
|
+
//
|
|
154
|
+
// A face naming the same vertex twice is different: its self-edge
|
|
155
|
+
// cannot be matched by anything, so it genuinely has to go.
|
|
156
|
+
if (repeatsAVertex(mesh, t)) continue;
|
|
143
157
|
Vert v[3];
|
|
144
158
|
triVerts(mesh, t, v);
|
|
145
159
|
|
|
@@ -190,6 +204,8 @@ std::string MeshRepairReport::summary() const {
|
|
|
190
204
|
+ (filledHoles == 1 ? " hole filled (" : " holes filled (")
|
|
191
205
|
+ std::to_string(filledTriangles) + " triangles)");
|
|
192
206
|
}
|
|
207
|
+
add(strippedSlivers, strippedSlivers == 1 ? "zero-area face stripped"
|
|
208
|
+
: "zero-area faces stripped");
|
|
193
209
|
add(unfilledHoles, unfilledHoles == 1 ? "hole left open" : "holes left open");
|
|
194
210
|
std::string out;
|
|
195
211
|
for (size_t i = 0; i < parts.size(); ++i) {
|
|
@@ -412,6 +428,206 @@ manifold::MeshGL repairMesh(const manifold::MeshGL& mesh, MeshRepairReport& repo
|
|
|
412
428
|
out.runTransform.clear();
|
|
413
429
|
out.mergeFromVert.clear();
|
|
414
430
|
out.mergeToVert.clear();
|
|
431
|
+
|
|
432
|
+
// Zero-area faces last. Welding already disposes of needles -- it
|
|
433
|
+
// collapses the coincident pair, leaving a face that names a vertex
|
|
434
|
+
// twice, which step 2 drops -- but a sliver whose three corners are
|
|
435
|
+
// distinct and collinear survives all of the above untouched, and an
|
|
436
|
+
// imported STL is exactly where those turn up.
|
|
437
|
+
SliverStripReport strip;
|
|
438
|
+
out = stripSlivers(out, strip);
|
|
439
|
+
report.strippedSlivers = strip.removed;
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
namespace {
|
|
445
|
+
|
|
446
|
+
// Which two corners of a zero-area face sit at the same position, if any.
|
|
447
|
+
// Such a face is a needle rather than a T-joint: its two long edges run
|
|
448
|
+
// between the same two points, so once it is gone the faces on either side
|
|
449
|
+
// already meet along one edge and nothing needs splitting. The coincident
|
|
450
|
+
// pair is merged so that is true by index as well as by position.
|
|
451
|
+
bool coincidentPair(const manifold::MeshGL& m, const std::array<Vert, 3>& f,
|
|
452
|
+
Vert& keep, Vert& drop) {
|
|
453
|
+
for (int i = 0; i < 3; ++i) {
|
|
454
|
+
const Vert a = f[i], b = f[(i + 1) % 3];
|
|
455
|
+
if (a == b) { keep = a; drop = b; return true; }
|
|
456
|
+
double p[3], q[3];
|
|
457
|
+
pos(m, a, p); pos(m, b, q);
|
|
458
|
+
if (llround(p[0] * 1e6) == llround(q[0] * 1e6)
|
|
459
|
+
&& llround(p[1] * 1e6) == llround(q[1] * 1e6)
|
|
460
|
+
&& llround(p[2] * 1e6) == llround(q[2] * 1e6)) {
|
|
461
|
+
keep = std::min(a, b);
|
|
462
|
+
drop = std::max(a, b);
|
|
463
|
+
return keep != drop;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Index of the vertex opposite the longest edge -- for three collinear
|
|
470
|
+
// points that is the one in the middle.
|
|
471
|
+
int middleOfCollinear(const manifold::MeshGL& m, const std::array<Vert, 3>& f) {
|
|
472
|
+
double p[3][3];
|
|
473
|
+
for (int i = 0; i < 3; ++i) pos(m, f[i], p[i]);
|
|
474
|
+
auto d2 = [&](int a, int b) {
|
|
475
|
+
double s = 0;
|
|
476
|
+
for (int k = 0; k < 3; ++k) { const double d = p[a][k] - p[b][k]; s += d * d; }
|
|
477
|
+
return s;
|
|
478
|
+
};
|
|
479
|
+
const double opp[3] = {d2(1, 2), d2(0, 2), d2(0, 1)};
|
|
480
|
+
int best = 0;
|
|
481
|
+
for (int i = 1; i < 3; ++i) if (opp[i] > opp[best]) best = i;
|
|
482
|
+
return best;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
} // namespace
|
|
486
|
+
|
|
487
|
+
manifold::MeshGL stripSlivers(const manifold::MeshGL& mesh, SliverStripReport& report) {
|
|
488
|
+
report = SliverStripReport{};
|
|
489
|
+
std::vector<std::array<Vert, 3>> tris;
|
|
490
|
+
tris.reserve(triCount(mesh));
|
|
491
|
+
for (size_t t = 0; t < triCount(mesh); ++t) {
|
|
492
|
+
Vert v[3];
|
|
493
|
+
triVerts(mesh, t, v);
|
|
494
|
+
tris.push_back({v[0], v[1], v[2]});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Removing a sliver can leave its neighbour split into pieces that are
|
|
498
|
+
// themselves slivers, so this repeats. Bounded because each pass must
|
|
499
|
+
// remove at least one face to continue.
|
|
500
|
+
const int kMaxPasses = 12;
|
|
501
|
+
for (int pass = 0; pass < kMaxPasses; ++pass) {
|
|
502
|
+
std::vector<size_t> slivers;
|
|
503
|
+
for (size_t i = 0; i < tris.size(); ++i) {
|
|
504
|
+
const auto& f = tris[i];
|
|
505
|
+
if (f[0] == f[1] || f[1] == f[2] || f[0] == f[2]) continue; // handled elsewhere
|
|
506
|
+
double a[3], b[3], c[3];
|
|
507
|
+
pos(mesh, f[0], a); pos(mesh, f[1], b); pos(mesh, f[2], c);
|
|
508
|
+
const double ux = b[0] - a[0], uy = b[1] - a[1], uz = b[2] - a[2];
|
|
509
|
+
const double wx = c[0] - a[0], wy = c[1] - a[1], wz = c[2] - a[2];
|
|
510
|
+
const double cx = uy * wz - uz * wy, cy = uz * wx - ux * wz, cz = ux * wy - uy * wx;
|
|
511
|
+
if ((cx * cx + cy * cy + cz * cz) < 1e-24) slivers.push_back(i);
|
|
512
|
+
}
|
|
513
|
+
if (slivers.empty()) break;
|
|
514
|
+
++report.passes;
|
|
515
|
+
|
|
516
|
+
// Which face owns each edge, so a sliver's long-edge neighbour can
|
|
517
|
+
// be found. Rebuilt per pass: splitting changes it.
|
|
518
|
+
std::map<Edge, std::vector<size_t>> owners;
|
|
519
|
+
for (size_t i = 0; i < tris.size(); ++i) {
|
|
520
|
+
for (int e = 0; e < 3; ++e) {
|
|
521
|
+
owners[undirected(tris[i][e], tris[i][(e + 1) % 3])].push_back(i);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
std::vector<char> isSliver(tris.size(), 0);
|
|
526
|
+
for (size_t i : slivers) isSliver[i] = 1;
|
|
527
|
+
|
|
528
|
+
std::vector<char> dead(tris.size(), 0);
|
|
529
|
+
std::vector<std::array<Vert, 3>> added;
|
|
530
|
+
std::map<Vert, Vert> merge; // needle corners to fold together
|
|
531
|
+
for (size_t si : slivers) {
|
|
532
|
+
if (dead[si]) continue;
|
|
533
|
+
const auto f = tris[si];
|
|
534
|
+
|
|
535
|
+
// A needle: two corners at one point. Nothing to restitch --
|
|
536
|
+
// the faces on either side already share an edge positionally,
|
|
537
|
+
// and merging the pair makes them share it by index too.
|
|
538
|
+
Vert keep = 0, drop = 0;
|
|
539
|
+
if (coincidentPair(mesh, f, keep, drop)) {
|
|
540
|
+
dead[si] = 1;
|
|
541
|
+
if (keep != drop) merge[drop] = keep;
|
|
542
|
+
++report.removed;
|
|
543
|
+
++report.needles;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const int mid = middleOfCollinear(mesh, f);
|
|
548
|
+
const Vert m = f[mid], a = f[(mid + 1) % 3], b = f[(mid + 2) % 3];
|
|
549
|
+
|
|
550
|
+
// The neighbour across the long edge a-b, which is the one the
|
|
551
|
+
// middle vertex now sits inside.
|
|
552
|
+
// Prefer a neighbour that is not itself a sliver. Two slivers
|
|
553
|
+
// sharing their long edge are each other's only candidate, and
|
|
554
|
+
// splitting one into the other just moves the problem around --
|
|
555
|
+
// a level-4 Menger sponge has exactly one such pair, and it was
|
|
556
|
+
// what stopped the last two from ever clearing.
|
|
557
|
+
size_t nb = SIZE_MAX, fallback = SIZE_MAX;
|
|
558
|
+
for (size_t cand : owners[undirected(a, b)]) {
|
|
559
|
+
if (cand == si || dead[cand]) continue;
|
|
560
|
+
if (isSliver[cand]) {
|
|
561
|
+
if (fallback == SIZE_MAX) fallback = cand;
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
nb = cand;
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
// A non-sliver neighbour is preferred but not required. Two
|
|
568
|
+
// slivers sharing their long edge are each other's only
|
|
569
|
+
// candidate -- a level-4 Menger sponge has one such pair, and
|
|
570
|
+
// refusing to split into a sliver leaves them forever. Splitting
|
|
571
|
+
// into one still makes progress, because the halves are smaller
|
|
572
|
+
// and the next pass reconsiders them.
|
|
573
|
+
if (nb == SIZE_MAX) nb = fallback;
|
|
574
|
+
if (nb == SIZE_MAX) { ++report.leftBehind; continue; }
|
|
575
|
+
|
|
576
|
+
// Split the neighbour at m, keeping its winding: the edge a-b
|
|
577
|
+
// appears in it in some direction, and the two pieces must
|
|
578
|
+
// traverse it the same way round.
|
|
579
|
+
const auto& n = tris[nb];
|
|
580
|
+
int e = -1;
|
|
581
|
+
for (int i = 0; i < 3; ++i) {
|
|
582
|
+
const Vert x = n[i], y = n[(i + 1) % 3];
|
|
583
|
+
if ((x == a && y == b) || (x == b && y == a)) { e = i; break; }
|
|
584
|
+
}
|
|
585
|
+
if (e < 0) { ++report.leftBehind; continue; }
|
|
586
|
+
const Vert x = n[e], y = n[(e + 1) % 3], apex = n[(e + 2) % 3];
|
|
587
|
+
|
|
588
|
+
dead[si] = 1;
|
|
589
|
+
dead[nb] = 1;
|
|
590
|
+
added.push_back({x, m, apex});
|
|
591
|
+
added.push_back({m, y, apex});
|
|
592
|
+
++report.removed;
|
|
593
|
+
++report.restitched;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
std::vector<std::array<Vert, 3>> next;
|
|
597
|
+
next.reserve(tris.size() + added.size());
|
|
598
|
+
auto resolve = [&](Vert v) {
|
|
599
|
+
for (int hop = 0; hop < 8; ++hop) { // chains are short
|
|
600
|
+
auto it = merge.find(v);
|
|
601
|
+
if (it == merge.end()) break;
|
|
602
|
+
v = it->second;
|
|
603
|
+
}
|
|
604
|
+
return v;
|
|
605
|
+
};
|
|
606
|
+
for (size_t i = 0; i < tris.size(); ++i) {
|
|
607
|
+
if (dead[i]) continue;
|
|
608
|
+
const auto& f = tris[i];
|
|
609
|
+
next.push_back({resolve(f[0]), resolve(f[1]), resolve(f[2])});
|
|
610
|
+
}
|
|
611
|
+
for (const auto& f : added) {
|
|
612
|
+
next.push_back({resolve(f[0]), resolve(f[1]), resolve(f[2])});
|
|
613
|
+
}
|
|
614
|
+
if (next.size() == tris.size() && added.empty()) break;
|
|
615
|
+
tris.swap(next);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
manifold::MeshGL out = mesh;
|
|
619
|
+
out.triVerts.clear();
|
|
620
|
+
out.triVerts.reserve(tris.size() * 3);
|
|
621
|
+
for (const auto& f : tris) {
|
|
622
|
+
out.triVerts.push_back(f[0]);
|
|
623
|
+
out.triVerts.push_back(f[1]);
|
|
624
|
+
out.triVerts.push_back(f[2]);
|
|
625
|
+
}
|
|
626
|
+
// These describe the old triangle list; a stale one is worse than none.
|
|
627
|
+
out.runIndex.clear();
|
|
628
|
+
out.runOriginalID.clear();
|
|
629
|
+
out.faceID.clear();
|
|
630
|
+
out.runTransform.clear();
|
|
415
631
|
return out;
|
|
416
632
|
}
|
|
417
633
|
|
|
@@ -97,6 +97,47 @@ TEST(MeshCheck, DegenerateAndDuplicateFacesAreReportedSeparately) {
|
|
|
97
97
|
EXPECT_EQ(d.duplicateFaces, 1u) << d.summary();
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// A sliver is still a face. Dropping zero-area triangles before building
|
|
101
|
+
// the edge map leaves their edges with one face each, which reads as a hole
|
|
102
|
+
// that is not there -- a level-4 Menger sponge is manifold with every face
|
|
103
|
+
// present and reported 722 boundary edges without them.
|
|
104
|
+
TEST(MeshCheck, AZeroAreaFaceIsReportedWithoutInventingHoles) {
|
|
105
|
+
// A closed cube whose top face is fanned through Q, a point sitting
|
|
106
|
+
// exactly on the diagonal 4-6. That makes face {4,8,6} zero-area while
|
|
107
|
+
// every edge it owns is still shared with a real neighbour -- which is
|
|
108
|
+
// the shape CSG actually produces. Drop the sliver and edges 4-8, 8-6
|
|
109
|
+
// and 4-6 each lose a face, so three holes appear that were never there.
|
|
110
|
+
manifold::MeshGL m = build(
|
|
111
|
+
{0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0,
|
|
112
|
+
0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1,
|
|
113
|
+
0.5f, 0.5f, 1}, // 8: on the diagonal 4-6
|
|
114
|
+
{
|
|
115
|
+
0, 2, 1, 0, 3, 2, // bottom
|
|
116
|
+
4, 5, 8, 5, 6, 8, 4, 8, 6, // top, fanned; {4,8,6} is the sliver
|
|
117
|
+
4, 6, 7,
|
|
118
|
+
0, 1, 5, 0, 5, 4, // sides
|
|
119
|
+
1, 2, 6, 1, 6, 5,
|
|
120
|
+
2, 3, 7, 2, 7, 6,
|
|
121
|
+
3, 0, 4, 3, 4, 7,
|
|
122
|
+
});
|
|
123
|
+
const MeshDiagnosis d = checkMesh(m);
|
|
124
|
+
EXPECT_EQ(d.degenerateFaces, 1u) << "fixture has no sliver: " << d.summary();
|
|
125
|
+
EXPECT_EQ(d.boundaryEdges, 0u)
|
|
126
|
+
<< "dropping the sliver invented holes: " << d.summary();
|
|
127
|
+
EXPECT_TRUE(d.watertight()) << d.summary();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// A face naming the same vertex twice is different: its self-edge cannot be
|
|
131
|
+
// matched by anything, so it does have to leave the topology.
|
|
132
|
+
TEST(MeshCheck, AFaceWithARepeatedVertexIsExcludedFromTheTopology) {
|
|
133
|
+
manifold::MeshGL m = tetra();
|
|
134
|
+
m.triVerts.insert(m.triVerts.end(), {0u, 1u, 1u});
|
|
135
|
+
const MeshDiagnosis d = checkMesh(m);
|
|
136
|
+
EXPECT_EQ(d.degenerateFaces, 1u) << d.summary();
|
|
137
|
+
EXPECT_EQ(d.boundaryEdges, 0u) << "its self-edge leaked in: " << d.summary();
|
|
138
|
+
EXPECT_TRUE(d.watertight()) << d.summary();
|
|
139
|
+
}
|
|
140
|
+
|
|
100
141
|
TEST(MeshCheck, CoincidentButDistinctVerticesAreReported) {
|
|
101
142
|
manifold::MeshGL m = tetra();
|
|
102
143
|
m.vertProperties.insert(m.vertProperties.end(), {0, 0, 0}); // copy of vertex 0
|
|
@@ -208,3 +249,130 @@ TEST(MeshRepair, ReportsWhatItDid) {
|
|
|
208
249
|
repairMesh(m, r);
|
|
209
250
|
EXPECT_NE(r.summary().find("hole filled"), std::string::npos) << r.summary();
|
|
210
251
|
}
|
|
252
|
+
|
|
253
|
+
// -- sliver stripping -----------------------------------------------------
|
|
254
|
+
|
|
255
|
+
// The cube whose top face is fanned through a point on its diagonal, from
|
|
256
|
+
// the check tests above: manifold, with one zero-area face.
|
|
257
|
+
namespace {
|
|
258
|
+
manifold::MeshGL cubeWithSliver() {
|
|
259
|
+
return build(
|
|
260
|
+
{0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0,
|
|
261
|
+
0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1,
|
|
262
|
+
0.5f, 0.5f, 1},
|
|
263
|
+
{
|
|
264
|
+
0, 2, 1, 0, 3, 2,
|
|
265
|
+
4, 5, 8, 5, 6, 8, 4, 8, 6,
|
|
266
|
+
4, 6, 7,
|
|
267
|
+
0, 1, 5, 0, 5, 4,
|
|
268
|
+
1, 2, 6, 1, 6, 5,
|
|
269
|
+
2, 3, 7, 2, 7, 6,
|
|
270
|
+
3, 0, 4, 3, 4, 7,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
double signedVolume(const manifold::MeshGL& m) {
|
|
275
|
+
double vol = 0;
|
|
276
|
+
for (size_t t = 0; t + 2 < m.triVerts.size(); t += 3) {
|
|
277
|
+
const float* a = &m.vertProperties[m.triVerts[t] * 3];
|
|
278
|
+
const float* b = &m.vertProperties[m.triVerts[t + 1] * 3];
|
|
279
|
+
const float* c = &m.vertProperties[m.triVerts[t + 2] * 3];
|
|
280
|
+
vol += (a[0] * (b[1] * c[2] - b[2] * c[1])
|
|
281
|
+
- a[1] * (b[0] * c[2] - b[2] * c[0])
|
|
282
|
+
+ a[2] * (b[0] * c[1] - b[1] * c[0])) / 6.0;
|
|
283
|
+
}
|
|
284
|
+
return vol;
|
|
285
|
+
}
|
|
286
|
+
} // namespace
|
|
287
|
+
|
|
288
|
+
TEST(SliverStrip, RemovesTheSliverAndRestitchesTheTJoint) {
|
|
289
|
+
const manifold::MeshGL m = cubeWithSliver();
|
|
290
|
+
ASSERT_EQ(checkMesh(m).degenerateFaces, 1u);
|
|
291
|
+
ASSERT_TRUE(checkMesh(m).manifold());
|
|
292
|
+
|
|
293
|
+
SliverStripReport r;
|
|
294
|
+
const manifold::MeshGL out = stripSlivers(m, r);
|
|
295
|
+
EXPECT_EQ(r.removed, 1u) << "sliver not removed";
|
|
296
|
+
EXPECT_EQ(r.restitched, 1u) << "neighbour not split";
|
|
297
|
+
|
|
298
|
+
const MeshDiagnosis d = checkMesh(out);
|
|
299
|
+
EXPECT_EQ(d.degenerateFaces, 0u) << d.summary();
|
|
300
|
+
// The point of restitching: simply deleting the face would leave the
|
|
301
|
+
// middle vertex sitting on the neighbour's edge, and three holes.
|
|
302
|
+
EXPECT_EQ(d.boundaryEdges, 0u) << "deleting alone left holes: " << d.summary();
|
|
303
|
+
EXPECT_TRUE(d.manifold()) << d.summary();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
TEST(SliverStrip, MovesNoGeometry) {
|
|
307
|
+
// A retriangulation, not a repair: the solid must be identical.
|
|
308
|
+
const manifold::MeshGL m = cubeWithSliver();
|
|
309
|
+
SliverStripReport r;
|
|
310
|
+
const manifold::MeshGL out = stripSlivers(m, r);
|
|
311
|
+
EXPECT_NEAR(signedVolume(out), signedVolume(m), 1e-9);
|
|
312
|
+
// Two faces out -- the sliver and the neighbour it was stuck to -- and
|
|
313
|
+
// the neighbour's two halves in. The count is unchanged.
|
|
314
|
+
EXPECT_EQ(out.triVerts.size() / 3, m.triVerts.size() / 3);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
TEST(SliverStrip, LeavesACleanMeshUntouched) {
|
|
318
|
+
SliverStripReport r;
|
|
319
|
+
const manifold::MeshGL out = stripSlivers(tetra(), r);
|
|
320
|
+
EXPECT_EQ(r.removed, 0u);
|
|
321
|
+
EXPECT_EQ(r.passes, 0u) << "walked a mesh with nothing to do";
|
|
322
|
+
EXPECT_EQ(out.triVerts, tetra().triVerts);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// A zero-area face with two corners at one point is a needle, not a
|
|
326
|
+
// T-joint: its two long edges run between the same pair of points, so the
|
|
327
|
+
// faces on either side already meet once it is gone. Splitting a neighbour
|
|
328
|
+
// for it would be wrong -- there is no middle vertex to split at.
|
|
329
|
+
TEST(SliverStrip, ANeedleIsRemovedWithoutSplittingAnything) {
|
|
330
|
+
manifold::MeshGL m = tetra();
|
|
331
|
+
// Vertex 4 sits exactly on vertex 1, and the needle {0,1,4} has two
|
|
332
|
+
// corners at that one point.
|
|
333
|
+
m.vertProperties.insert(m.vertProperties.end(), {1, 0, 0});
|
|
334
|
+
m.triVerts.insert(m.triVerts.end(), {0u, 1u, 4u});
|
|
335
|
+
ASSERT_GT(checkMesh(m).degenerateFaces, 0u);
|
|
336
|
+
|
|
337
|
+
SliverStripReport r;
|
|
338
|
+
const manifold::MeshGL out = stripSlivers(m, r);
|
|
339
|
+
EXPECT_EQ(r.needles, 1u) << "not recognised as a needle: " << r.removed;
|
|
340
|
+
EXPECT_EQ(r.restitched, 0u) << "split a neighbour it did not need to";
|
|
341
|
+
|
|
342
|
+
const MeshDiagnosis d = checkMesh(out);
|
|
343
|
+
EXPECT_EQ(d.degenerateFaces, 0u) << d.summary();
|
|
344
|
+
EXPECT_EQ(d.boundaryEdges, 0u) << "removal left holes: " << d.summary();
|
|
345
|
+
EXPECT_TRUE(d.manifold()) << d.summary();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// import(repair=true) goes through repairMesh, so it has to deal with
|
|
349
|
+
// slivers too -- an imported STL is exactly where they turn up.
|
|
350
|
+
TEST(MeshRepair, StripsSliversAndRestitchesThem) {
|
|
351
|
+
const manifold::MeshGL m = cubeWithSliver();
|
|
352
|
+
ASSERT_EQ(checkMesh(m).degenerateFaces, 1u);
|
|
353
|
+
|
|
354
|
+
MeshRepairReport r;
|
|
355
|
+
const manifold::MeshGL out = repairMesh(m, r);
|
|
356
|
+
const MeshDiagnosis d = checkMesh(out);
|
|
357
|
+
EXPECT_EQ(d.degenerateFaces, 0u) << "sliver survived repair: " << d.summary();
|
|
358
|
+
EXPECT_EQ(d.boundaryEdges, 0u) << "removal left holes: " << d.summary();
|
|
359
|
+
EXPECT_TRUE(d.manifold()) << d.summary();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// A needle needs no restitching, and welding alone already handles it:
|
|
363
|
+
// collapsing the coincident pair turns the face into one naming a vertex
|
|
364
|
+
// twice, which repair drops. Worth pinning, because it is the reason
|
|
365
|
+
// repair coped with needles before it knew about slivers at all.
|
|
366
|
+
TEST(MeshRepair, WeldingAloneDisposesOfANeedle) {
|
|
367
|
+
manifold::MeshGL m = tetra();
|
|
368
|
+
m.vertProperties.insert(m.vertProperties.end(), {1, 0, 0}); // == vertex 1
|
|
369
|
+
m.triVerts.insert(m.triVerts.end(), {0u, 1u, 4u});
|
|
370
|
+
|
|
371
|
+
MeshRepairReport r;
|
|
372
|
+
const manifold::MeshGL out = repairMesh(m, r);
|
|
373
|
+
EXPECT_GT(r.weldedVertices, 0u) << r.summary();
|
|
374
|
+
EXPECT_GT(r.droppedDegenerate, 0u) << r.summary();
|
|
375
|
+
const MeshDiagnosis d = checkMesh(out);
|
|
376
|
+
EXPECT_EQ(d.degenerateFaces, 0u) << d.summary();
|
|
377
|
+
EXPECT_TRUE(d.manifold()) << d.summary();
|
|
378
|
+
}
|
|
File without changes
|
{openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/.github/workflows/wheels.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/examples/eval_perf_benchmark.cpp
RENAMED
|
File without changes
|
{openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/examples/manifold_cache_reuse.cpp
RENAMED
|
File without changes
|
{openscad_cpp_evaluator-0.23.0 → openscad_cpp_evaluator-0.25.0}/examples/minimal_debugger.cpp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|