openscad-cpp-evaluator 0.28.2__tar.gz → 0.29.1__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.28.2 → openscad_cpp_evaluator-0.29.1}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/primitives_3d.cpp +159 -8
- openscad_cpp_evaluator-0.29.1/src/builtins/topology.cpp +246 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_booleans.cpp +213 -0
- openscad_cpp_evaluator-0.28.2/src/builtins/topology.cpp +0 -116
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/.gitignore +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/.gitmodules +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/LICENSE +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/README.md +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/text/stb_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/uv.lock +0 -0
{openscad_cpp_evaluator-0.28.2 → openscad_cpp_evaluator-0.29.1}/src/builtins/primitives_3d.cpp
RENAMED
|
@@ -239,6 +239,164 @@ std::vector<ColoredBody> generateCylinder(Evaluator& ev, const CSGParams& params
|
|
|
239
239
|
// otherwise produce a NotManifold body). Mirrors
|
|
240
240
|
// _resolve_polyhedron/_generate_polyhedron.
|
|
241
241
|
|
|
242
|
+
|
|
243
|
+
namespace {
|
|
244
|
+
|
|
245
|
+
// Triangulate one polyhedron face.
|
|
246
|
+
//
|
|
247
|
+
// A fan -- (v0, vi, vi+1) for every i -- is only correct for a polygon
|
|
248
|
+
// that is both convex and planar, and BOSL2's vnf_polyhedron() routinely
|
|
249
|
+
// hands over neither. The end caps of a nurbs_sheet() are 34-gons that
|
|
250
|
+
// are concave and 3.5 units out of plane; fanning one produced 32
|
|
251
|
+
// triangles covering 281% of the cap's true area with 15 of them wound
|
|
252
|
+
// inside out, which inflated the finished solid by 12% of its surface
|
|
253
|
+
// area and 5% of its volume.
|
|
254
|
+
//
|
|
255
|
+
// Ear clipping in the face's own best-fit plane instead. Newell's method
|
|
256
|
+
// gives a normal that stays meaningful when the points are not coplanar,
|
|
257
|
+
// which is what makes projecting them usable at all here.
|
|
258
|
+
//
|
|
259
|
+
// ponytail: O(n^2). Faces are a handful of points in nearly every model
|
|
260
|
+
// and 34 in the one that prompted this; revisit if a model ever arrives
|
|
261
|
+
// with thousand-sided faces.
|
|
262
|
+
void triangulateFace(const std::vector<std::array<double, 3>>& verts, const std::vector<size_t>& loop,
|
|
263
|
+
std::vector<uint32_t>& out) {
|
|
264
|
+
const size_t n = loop.size();
|
|
265
|
+
if (n < 3) return;
|
|
266
|
+
|
|
267
|
+
// Winding is reversed on the way out throughout: OpenSCAD's faces are
|
|
268
|
+
// clockwise seen from outside, Manifold wants counter-clockwise.
|
|
269
|
+
auto emit = [&out](size_t a, size_t b, size_t c) {
|
|
270
|
+
if (a == b || b == c || a == c) return;
|
|
271
|
+
out.push_back(static_cast<uint32_t>(a));
|
|
272
|
+
out.push_back(static_cast<uint32_t>(c));
|
|
273
|
+
out.push_back(static_cast<uint32_t>(b));
|
|
274
|
+
};
|
|
275
|
+
if (n == 3) {
|
|
276
|
+
emit(loop[0], loop[1], loop[2]);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
std::array<double, 3> nrm = {0.0, 0.0, 0.0};
|
|
281
|
+
for (size_t i = 0; i < n; ++i) {
|
|
282
|
+
const std::array<double, 3>& a = verts[loop[i]];
|
|
283
|
+
const std::array<double, 3>& b = verts[loop[(i + 1) % n]];
|
|
284
|
+
nrm[0] += (a[1] - b[1]) * (a[2] + b[2]);
|
|
285
|
+
nrm[1] += (a[2] - b[2]) * (a[0] + b[0]);
|
|
286
|
+
nrm[2] += (a[0] - b[0]) * (a[1] + b[1]);
|
|
287
|
+
}
|
|
288
|
+
const double len = std::sqrt(nrm[0] * nrm[0] + nrm[1] * nrm[1] + nrm[2] * nrm[2]);
|
|
289
|
+
if (!(len > 1e-12)) {
|
|
290
|
+
// Every point collinear, or the loop encloses no area: a fan is as
|
|
291
|
+
// good as anything and cannot make it worse.
|
|
292
|
+
for (size_t i = 1; i + 1 < n; ++i) emit(loop[0], loop[i], loop[i + 1]);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
for (double& c : nrm) c /= len;
|
|
296
|
+
|
|
297
|
+
// Any two axes spanning the plane will do; take the world axis least
|
|
298
|
+
// aligned with the normal so the projection never collapses.
|
|
299
|
+
const size_t drop = (std::abs(nrm[0]) > std::abs(nrm[1]))
|
|
300
|
+
? ((std::abs(nrm[0]) > std::abs(nrm[2])) ? 0 : 2)
|
|
301
|
+
: ((std::abs(nrm[1]) > std::abs(nrm[2])) ? 1 : 2);
|
|
302
|
+
std::array<double, 3> axis = {0.0, 0.0, 0.0};
|
|
303
|
+
axis[(drop + 1) % 3] = 1.0;
|
|
304
|
+
std::array<double, 3> u = {axis[1] * nrm[2] - axis[2] * nrm[1], axis[2] * nrm[0] - axis[0] * nrm[2],
|
|
305
|
+
axis[0] * nrm[1] - axis[1] * nrm[0]};
|
|
306
|
+
const double ulen = std::sqrt(u[0] * u[0] + u[1] * u[1] + u[2] * u[2]);
|
|
307
|
+
if (!(ulen > 1e-12)) {
|
|
308
|
+
for (size_t i = 1; i + 1 < n; ++i) emit(loop[0], loop[i], loop[i + 1]);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
for (double& c : u) c /= ulen;
|
|
312
|
+
const std::array<double, 3> w = {nrm[1] * u[2] - nrm[2] * u[1], nrm[2] * u[0] - nrm[0] * u[2],
|
|
313
|
+
nrm[0] * u[1] - nrm[1] * u[0]};
|
|
314
|
+
|
|
315
|
+
std::vector<std::array<double, 2>> flat(n);
|
|
316
|
+
for (size_t i = 0; i < n; ++i) {
|
|
317
|
+
const std::array<double, 3>& p = verts[loop[i]];
|
|
318
|
+
flat[i] = {p[0] * u[0] + p[1] * u[1] + p[2] * u[2], p[0] * w[0] + p[1] * w[1] + p[2] * w[2]};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
auto cross2 = [](const std::array<double, 2>& a, const std::array<double, 2>& b,
|
|
322
|
+
const std::array<double, 2>& c) {
|
|
323
|
+
return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
|
|
324
|
+
};
|
|
325
|
+
double twiceArea = 0.0;
|
|
326
|
+
for (size_t i = 0; i < n; ++i) {
|
|
327
|
+
const std::array<double, 2>& a = flat[i];
|
|
328
|
+
const std::array<double, 2>& b = flat[(i + 1) % n];
|
|
329
|
+
twiceArea += a[0] * b[1] - b[0] * a[1];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
std::vector<size_t> idx(n);
|
|
333
|
+
for (size_t i = 0; i < n; ++i) idx[i] = i;
|
|
334
|
+
if (twiceArea < 0.0) std::reverse(idx.begin(), idx.end()); // work counter-clockwise
|
|
335
|
+
|
|
336
|
+
auto inside = [&](const std::array<double, 2>& a, const std::array<double, 2>& b,
|
|
337
|
+
const std::array<double, 2>& c, const std::array<double, 2>& p) {
|
|
338
|
+
// Strictly inside, so a vertex sitting exactly on an edge does not
|
|
339
|
+
// veto an otherwise good ear.
|
|
340
|
+
const double d1 = cross2(a, b, p), d2 = cross2(b, c, p), d3 = cross2(c, a, p);
|
|
341
|
+
return d1 > 1e-12 && d2 > 1e-12 && d3 > 1e-12;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// Take the best-shaped ear available rather than the first one found.
|
|
345
|
+
// Any valid ear gives a correct triangulation, but on a face that is
|
|
346
|
+
// not flat the choice decides how the surface folds: first-found
|
|
347
|
+
// clipping strung long thin triangles across the curved end caps and
|
|
348
|
+
// came out 29% larger in area than the reference's tessellation of the
|
|
349
|
+
// same polygon. Preferring fat ears tracks the surface instead.
|
|
350
|
+
auto squareness = [&](size_t a, size_t b, size_t c) {
|
|
351
|
+
// Twice the area over the sum of the squared sides -- highest for
|
|
352
|
+
// an equilateral triangle, near zero for a sliver.
|
|
353
|
+
const std::array<double, 3>& p = verts[loop[a]];
|
|
354
|
+
const std::array<double, 3>& q = verts[loop[b]];
|
|
355
|
+
const std::array<double, 3>& r = verts[loop[c]];
|
|
356
|
+
const std::array<double, 3> e1 = {q[0] - p[0], q[1] - p[1], q[2] - p[2]};
|
|
357
|
+
const std::array<double, 3> e2 = {r[0] - p[0], r[1] - p[1], r[2] - p[2]};
|
|
358
|
+
const std::array<double, 3> e3 = {r[0] - q[0], r[1] - q[1], r[2] - q[2]};
|
|
359
|
+
const std::array<double, 3> x = {e1[1] * e2[2] - e1[2] * e2[1], e1[2] * e2[0] - e1[0] * e2[2],
|
|
360
|
+
e1[0] * e2[1] - e1[1] * e2[0]};
|
|
361
|
+
const double area = std::sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
|
|
362
|
+
const double sides = e1[0] * e1[0] + e1[1] * e1[1] + e1[2] * e1[2] +
|
|
363
|
+
e2[0] * e2[0] + e2[1] * e2[1] + e2[2] * e2[2] +
|
|
364
|
+
e3[0] * e3[0] + e3[1] * e3[1] + e3[2] * e3[2];
|
|
365
|
+
return sides > 1e-18 ? area / sides : 0.0;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
size_t guard = 0;
|
|
369
|
+
while (idx.size() > 3 && guard++ < n * n) {
|
|
370
|
+
size_t bestAt = idx.size();
|
|
371
|
+
double bestScore = -1.0;
|
|
372
|
+
for (size_t i = 0; i < idx.size(); ++i) {
|
|
373
|
+
const size_t pi = idx[(i + idx.size() - 1) % idx.size()];
|
|
374
|
+
const size_t ci = idx[i];
|
|
375
|
+
const size_t ni = idx[(i + 1) % idx.size()];
|
|
376
|
+
if (cross2(flat[pi], flat[ci], flat[ni]) <= 1e-12) continue; // reflex, not an ear
|
|
377
|
+
bool empty = true;
|
|
378
|
+
for (size_t other : idx) {
|
|
379
|
+
if (other == pi || other == ci || other == ni) continue;
|
|
380
|
+
if (inside(flat[pi], flat[ci], flat[ni], flat[other])) { empty = false; break; }
|
|
381
|
+
}
|
|
382
|
+
if (!empty) continue;
|
|
383
|
+
const double score = squareness(pi, ci, ni);
|
|
384
|
+
if (score > bestScore) { bestScore = score; bestAt = i; }
|
|
385
|
+
}
|
|
386
|
+
if (bestAt == idx.size()) break; // self-intersecting or otherwise unclippable
|
|
387
|
+
const size_t pi = idx[(bestAt + idx.size() - 1) % idx.size()];
|
|
388
|
+
const size_t ci = idx[bestAt];
|
|
389
|
+
const size_t ni = idx[(bestAt + 1) % idx.size()];
|
|
390
|
+
emit(loop[pi], loop[ci], loop[ni]);
|
|
391
|
+
idx.erase(idx.begin() + static_cast<long>(bestAt));
|
|
392
|
+
}
|
|
393
|
+
// Whatever is left: three points, or a remainder no ear could be found
|
|
394
|
+
// in. A fan over the remainder is the best available answer.
|
|
395
|
+
for (size_t i = 1; i + 1 < idx.size(); ++i) emit(loop[idx[0]], loop[idx[i]], loop[idx[i + 1]]);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
} // namespace
|
|
399
|
+
|
|
242
400
|
CSGParams resolvePolyhedron(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
|
|
243
401
|
auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
|
|
244
402
|
Value pointsArg = getArg(args, 0, "points", Value{});
|
|
@@ -292,14 +450,7 @@ CSGParams resolvePolyhedron(Evaluator& ev, const oscad::ModularCall& node, EvalC
|
|
|
292
450
|
const size_t idx = static_cast<size_t>(toDoubleLenient(idxVal));
|
|
293
451
|
remapped.push_back(idx < remap.size() ? remap[idx] : 0);
|
|
294
452
|
}
|
|
295
|
-
|
|
296
|
-
const size_t a = remapped[0], b = remapped[i + 1], c = remapped[i];
|
|
297
|
-
if (a != b && b != c && a != c) {
|
|
298
|
-
tris.push_back(static_cast<uint32_t>(a));
|
|
299
|
-
tris.push_back(static_cast<uint32_t>(b));
|
|
300
|
-
tris.push_back(static_cast<uint32_t>(c));
|
|
301
|
-
}
|
|
302
|
-
}
|
|
453
|
+
triangulateFace(uniqueVerts, remapped, tris);
|
|
303
454
|
}
|
|
304
455
|
|
|
305
456
|
std::vector<Value> vertsValues;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#include "builtins.hpp"
|
|
2
|
+
|
|
3
|
+
#include "openscad_cpp_evaluator/call_args.hpp"
|
|
4
|
+
#include "openscad_cpp_evaluator/evaluator.hpp"
|
|
5
|
+
|
|
6
|
+
#include <manifold/polygon.h>
|
|
7
|
+
|
|
8
|
+
namespace oscadeval {
|
|
9
|
+
|
|
10
|
+
// hull()/minkowski() -- like union/difference/intersection, these splice
|
|
11
|
+
// their children transparently in the CSG tree (resolve just evaluates
|
|
12
|
+
// them for the side effect); the actual hull/minkowski-sum only happens
|
|
13
|
+
// once every child's real geometry exists, in generate. Mirrors
|
|
14
|
+
// _resolve_hull/_resolve_minkowski (both bodies of the same one-line
|
|
15
|
+
// "evaluate children, return {}" shape as _resolve_transform).
|
|
16
|
+
|
|
17
|
+
CSGParams resolveHull(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
|
|
18
|
+
auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
|
|
19
|
+
(void)args;
|
|
20
|
+
ev.evalChildren(node.children, effCtx);
|
|
21
|
+
return CSGParams{};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// hull() of the foreground children: an all-3D hull if any child has a
|
|
25
|
+
// body, else an all-2D hull over sections -- mirrors _generate_hull exactly
|
|
26
|
+
// (a 2D/3D mix only ever hulls the 3D bodies, dropping any 2D sibling,
|
|
27
|
+
// matching the reference's own `if bodies_3d: ... else: sections...`
|
|
28
|
+
// either/or dispatch). background/highlight/show_only bodies pass through
|
|
29
|
+
// untouched, same as union/difference/intersection.
|
|
30
|
+
std::vector<ColoredBody> generateHull(Evaluator&, const CSGParams&, const std::vector<std::unique_ptr<CSGNode>>& children,
|
|
31
|
+
const oscad::ASTNode&) {
|
|
32
|
+
const std::vector<ColoredBody> bodies = flattenCsgTree(children);
|
|
33
|
+
if (bodies.empty()) return {};
|
|
34
|
+
const RoleSplit split = splitByRole(bodies);
|
|
35
|
+
|
|
36
|
+
std::optional<ColoredBody> hullResult;
|
|
37
|
+
if (!split.foreground.empty()) {
|
|
38
|
+
std::vector<manifold::Manifold> bodies3d;
|
|
39
|
+
for (const ColoredBody& c : split.foreground) {
|
|
40
|
+
if (c.body) bodies3d.push_back(*c.body);
|
|
41
|
+
}
|
|
42
|
+
if (!bodies3d.empty()) {
|
|
43
|
+
ColoredBody cb;
|
|
44
|
+
cb.body = manifold::Manifold::Hull(bodies3d);
|
|
45
|
+
cb.color = split.foreground.front().color;
|
|
46
|
+
hullResult = std::move(cb);
|
|
47
|
+
} else {
|
|
48
|
+
std::vector<manifold::CrossSection> sections;
|
|
49
|
+
for (const ColoredBody& c : split.foreground) {
|
|
50
|
+
if (c.section) sections.push_back(*c.section);
|
|
51
|
+
}
|
|
52
|
+
if (!sections.empty()) {
|
|
53
|
+
ColoredBody cb;
|
|
54
|
+
cb.section = manifold::CrossSection::Hull(sections);
|
|
55
|
+
cb.color = split.foreground.front().color;
|
|
56
|
+
hullResult = std::move(cb);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
std::vector<ColoredBody> result;
|
|
62
|
+
if (hullResult) result.push_back(std::move(*hullResult));
|
|
63
|
+
result.insert(result.end(), split.background.begin(), split.background.end());
|
|
64
|
+
result.insert(result.end(), split.highlight.begin(), split.highlight.end());
|
|
65
|
+
result.insert(result.end(), split.showOnly.begin(), split.showOnly.end());
|
|
66
|
+
result.insert(result.end(), split.displayOnly.begin(), split.displayOnly.end());
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
CSGParams resolveMinkowski(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
|
|
71
|
+
auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
|
|
72
|
+
(void)args;
|
|
73
|
+
ev.evalChildren(node.children, effCtx);
|
|
74
|
+
return CSGParams{};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
namespace {
|
|
79
|
+
|
|
80
|
+
// The convex pieces of a 2D shape, as point lists.
|
|
81
|
+
//
|
|
82
|
+
// A Minkowski sum only has a closed form for convex operands -- there it
|
|
83
|
+
// is the convex hull of every pairwise sum -- so a shape that is not
|
|
84
|
+
// convex has to be cut into pieces that are. A convex outline with no
|
|
85
|
+
// holes is already one piece, which is the common case (a circle being
|
|
86
|
+
// swept over something) and much cheaper than triangulating it.
|
|
87
|
+
std::vector<manifold::SimplePolygon> convexPieces(const manifold::CrossSection& section) {
|
|
88
|
+
const manifold::Polygons polys = section.ToPolygons();
|
|
89
|
+
std::vector<manifold::SimplePolygon> out;
|
|
90
|
+
|
|
91
|
+
if (polys.size() == 1) {
|
|
92
|
+
const manifold::SimplePolygon& ring = polys[0];
|
|
93
|
+
bool convex = ring.size() >= 3;
|
|
94
|
+
int sign = 0;
|
|
95
|
+
for (size_t i = 0; convex && i < ring.size(); ++i) {
|
|
96
|
+
const manifold::vec2& a = ring[i];
|
|
97
|
+
const manifold::vec2& b = ring[(i + 1) % ring.size()];
|
|
98
|
+
const manifold::vec2& c = ring[(i + 2) % ring.size()];
|
|
99
|
+
const double cross = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
|
100
|
+
if (std::abs(cross) < 1e-12) continue; // collinear, no turn
|
|
101
|
+
const int s = cross > 0 ? 1 : -1;
|
|
102
|
+
if (sign == 0) sign = s;
|
|
103
|
+
else if (s != sign) convex = false;
|
|
104
|
+
}
|
|
105
|
+
if (convex) {
|
|
106
|
+
out.push_back(ring);
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const manifold::ivec3& tri : manifold::Triangulate(polys)) {
|
|
112
|
+
// Triangulate indexes the contours end to end.
|
|
113
|
+
std::vector<manifold::vec2> flat;
|
|
114
|
+
for (const manifold::SimplePolygon& ring : polys)
|
|
115
|
+
flat.insert(flat.end(), ring.begin(), ring.end());
|
|
116
|
+
if (static_cast<size_t>(tri.x) >= flat.size() || static_cast<size_t>(tri.y) >= flat.size() ||
|
|
117
|
+
static_cast<size_t>(tri.z) >= flat.size()) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
out.push_back({flat[tri.x], flat[tri.y], flat[tri.z]});
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// The 2D Minkowski sum of two shapes.
|
|
126
|
+
//
|
|
127
|
+
// Manifold has no 2D Minkowski, so this is the boundary-sweep identity:
|
|
128
|
+
// for B convex and containing the origin,
|
|
129
|
+
//
|
|
130
|
+
// A (+) B = A union (boundary of A (+) B)
|
|
131
|
+
//
|
|
132
|
+
// and the boundary is a chain of segments, each of which sums with a
|
|
133
|
+
// convex B to the hull of B at its two ends. So sweeping B along every
|
|
134
|
+
// edge of A and unioning A itself is the whole answer -- A is never cut
|
|
135
|
+
// up, however concave it is or however many holes it has. Only B is,
|
|
136
|
+
// and only because the per-segment hull needs it convex.
|
|
137
|
+
//
|
|
138
|
+
// The two conditions on B are met rather than assumed: it is decomposed
|
|
139
|
+
// into convex pieces (Minkowski distributes over union, so the pieces'
|
|
140
|
+
// sums are unioned), and each piece is shifted onto the origin with the
|
|
141
|
+
// shift undone afterwards, since A (+) B = ((A (+) (B - c)) + c).
|
|
142
|
+
//
|
|
143
|
+
// ponytail: one hull per edge of A per convex piece of B. A piece count
|
|
144
|
+
// of one is the case that turns up -- a circle swept over something --
|
|
145
|
+
// and then it is simply one hull per edge.
|
|
146
|
+
manifold::CrossSection minkowski2d(const manifold::CrossSection& a, const manifold::CrossSection& b) {
|
|
147
|
+
const manifold::Polygons outline = a.ToPolygons();
|
|
148
|
+
std::vector<manifold::CrossSection> parts;
|
|
149
|
+
|
|
150
|
+
for (const manifold::SimplePolygon& piece : convexPieces(b)) {
|
|
151
|
+
if (piece.size() < 3) continue;
|
|
152
|
+
// Bring the piece onto the origin; the sum is shifted back after.
|
|
153
|
+
manifold::vec2 shift = piece[0];
|
|
154
|
+
manifold::SimplePolygon centred;
|
|
155
|
+
centred.reserve(piece.size());
|
|
156
|
+
for (const manifold::vec2& q : piece) centred.push_back({q.x - shift.x, q.y - shift.y});
|
|
157
|
+
|
|
158
|
+
std::vector<manifold::CrossSection> swept;
|
|
159
|
+
// A itself: only sound because `centred` contains the origin.
|
|
160
|
+
swept.push_back(a);
|
|
161
|
+
for (const manifold::SimplePolygon& ring : outline) {
|
|
162
|
+
for (size_t i = 0; i < ring.size(); ++i) {
|
|
163
|
+
const manifold::vec2& v0 = ring[i];
|
|
164
|
+
const manifold::vec2& v1 = ring[(i + 1) % ring.size()];
|
|
165
|
+
manifold::SimplePolygon ends;
|
|
166
|
+
ends.reserve(centred.size() * 2);
|
|
167
|
+
for (const manifold::vec2& q : centred) {
|
|
168
|
+
ends.push_back({v0.x + q.x, v0.y + q.y});
|
|
169
|
+
ends.push_back({v1.x + q.x, v1.y + q.y});
|
|
170
|
+
}
|
|
171
|
+
swept.push_back(manifold::CrossSection::Hull(ends));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
manifold::CrossSection sum =
|
|
175
|
+
manifold::CrossSection::BatchBoolean(swept, manifold::OpType::Add);
|
|
176
|
+
parts.push_back(sum.Translate(shift));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (parts.empty()) return manifold::CrossSection();
|
|
180
|
+
return manifold::CrossSection::BatchBoolean(parts, manifold::OpType::Add);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
} // namespace
|
|
184
|
+
|
|
185
|
+
// minkowski() over 3D bodies, or over 2D sections when that is what it
|
|
186
|
+
// was given. 2D used to be dropped on the floor -- silently, so
|
|
187
|
+
// `linear_extrude() minkowski() { square(); circle(); }` produced nothing
|
|
188
|
+
// at all where the reference produces the rounded square you asked for.
|
|
189
|
+
std::vector<ColoredBody> generateMinkowski(Evaluator& ev, const CSGParams&, const std::vector<std::unique_ptr<CSGNode>>& children,
|
|
190
|
+
const oscad::ASTNode& node) {
|
|
191
|
+
const std::vector<ColoredBody> bodies = flattenCsgTree(children);
|
|
192
|
+
const RoleSplit split = splitByRole(bodies);
|
|
193
|
+
|
|
194
|
+
std::vector<const ColoredBody*> bodies3d;
|
|
195
|
+
std::vector<const ColoredBody*> sections2d;
|
|
196
|
+
for (const ColoredBody& c : split.foreground) {
|
|
197
|
+
if (c.body) bodies3d.push_back(&c);
|
|
198
|
+
else if (c.section) sections2d.push_back(&c);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
std::vector<ColoredBody> passthrough;
|
|
202
|
+
passthrough.insert(passthrough.end(), split.background.begin(), split.background.end());
|
|
203
|
+
passthrough.insert(passthrough.end(), split.highlight.begin(), split.highlight.end());
|
|
204
|
+
passthrough.insert(passthrough.end(), split.showOnly.begin(), split.showOnly.end());
|
|
205
|
+
passthrough.insert(passthrough.end(), split.displayOnly.begin(), split.displayOnly.end());
|
|
206
|
+
|
|
207
|
+
// 2D only: sum the sections instead. A mix of 2D and 3D is the
|
|
208
|
+
// reference's error case, and the 3D bodies win here as they do there.
|
|
209
|
+
if (bodies3d.empty() && sections2d.size() >= 2) {
|
|
210
|
+
manifold::CrossSection acc = *sections2d.front()->section;
|
|
211
|
+
for (size_t i = 1; i < sections2d.size(); ++i)
|
|
212
|
+
acc = minkowski2d(acc, *sections2d[i]->section);
|
|
213
|
+
ColoredBody cb;
|
|
214
|
+
cb.section = std::move(acc);
|
|
215
|
+
cb.color = sections2d.front()->color;
|
|
216
|
+
std::vector<ColoredBody> out = {std::move(cb)};
|
|
217
|
+
out.insert(out.end(), passthrough.begin(), passthrough.end());
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
if (bodies3d.empty() && sections2d.size() == 1) {
|
|
221
|
+
std::vector<ColoredBody> out = {*sections2d.front()};
|
|
222
|
+
out.insert(out.end(), passthrough.begin(), passthrough.end());
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
if (bodies3d.empty()) return passthrough;
|
|
226
|
+
if (bodies3d.size() == 1) {
|
|
227
|
+
std::vector<ColoredBody> result = {*bodies3d.front()};
|
|
228
|
+
result.insert(result.end(), passthrough.begin(), passthrough.end());
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
manifold::Manifold result = *bodies3d.front()->body;
|
|
233
|
+
for (size_t i = 1; i < bodies3d.size(); ++i) result = result.MinkowskiSum(*bodies3d[i]->body);
|
|
234
|
+
if (result.Status() != manifold::Manifold::Error::NoError) {
|
|
235
|
+
ev.warn("minkowski: result is not manifold", &node.position());
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
ColoredBody cb;
|
|
239
|
+
cb.body = std::move(result);
|
|
240
|
+
cb.color = bodies3d.front()->color;
|
|
241
|
+
std::vector<ColoredBody> out = {std::move(cb)};
|
|
242
|
+
out.insert(out.end(), passthrough.begin(), passthrough.end());
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
} // namespace oscadeval
|
|
@@ -227,3 +227,216 @@ TEST(Modifiers, NoShowOnlyMeansAllRolesPassThrough) {
|
|
|
227
227
|
Evaluated e = evaluateSrc("%cube(1); cube(2);");
|
|
228
228
|
EXPECT_EQ(e.bodies.size(), 2u);
|
|
229
229
|
}
|
|
230
|
+
|
|
231
|
+
// -- polyhedron face triangulation ---------------------------------------
|
|
232
|
+
|
|
233
|
+
// An L-shaped prism: its top and bottom are 6-gons with one reflex corner.
|
|
234
|
+
// Fan-triangulating a concave face lays triangles outside the polygon and
|
|
235
|
+
// winds some of them inside out, so the solid comes out the wrong size --
|
|
236
|
+
// which is how BOSL2's nurbs_sheet() end caps ended up covering 281% of
|
|
237
|
+
// their own area with 15 of 32 triangles reversed.
|
|
238
|
+
// A U: vertex 0 cannot see the far arm, so a fan anchored there lays
|
|
239
|
+
// triangles across the notch, outside the solid. (An L is not enough on
|
|
240
|
+
// its own -- it happens to be star-shaped from its first vertex, so even
|
|
241
|
+
// a fan triangulates it correctly, which is why the volume check below
|
|
242
|
+
// needs this shape too.)
|
|
243
|
+
static const char* kUPrism = R"(
|
|
244
|
+
pts = [[0,0],[3,0],[3,3],[2,3],[2,1],[1,1],[1,3],[0,3]];
|
|
245
|
+
polyhedron(
|
|
246
|
+
points = concat([for (p=pts) [p[0],p[1],0]], [for (p=pts) [p[0],p[1],1]]),
|
|
247
|
+
faces = concat(
|
|
248
|
+
[[for (i=[0:len(pts)-1]) i]],
|
|
249
|
+
[[for (i=[len(pts)-1:-1:0]) i+len(pts)]],
|
|
250
|
+
[for (i=[0:len(pts)-1]) [i, i+len(pts), (i+1)%len(pts)+len(pts), (i+1)%len(pts)]]
|
|
251
|
+
)
|
|
252
|
+
);
|
|
253
|
+
)";
|
|
254
|
+
|
|
255
|
+
static const char* kLPrism = R"(
|
|
256
|
+
polyhedron(
|
|
257
|
+
points = [
|
|
258
|
+
[0,0,0], [2,0,0], [2,1,0], [1,1,0], [1,2,0], [0,2,0],
|
|
259
|
+
[0,0,1], [2,0,1], [2,1,1], [1,1,1], [1,2,1], [0,2,1]
|
|
260
|
+
],
|
|
261
|
+
faces = [
|
|
262
|
+
[0,1,2,3,4,5],
|
|
263
|
+
[11,10,9,8,7,6],
|
|
264
|
+
[0,6,7,1], [1,7,8,2], [2,8,9,3],
|
|
265
|
+
[3,9,10,4], [4,10,11,5], [5,11,6,0]
|
|
266
|
+
]
|
|
267
|
+
);
|
|
268
|
+
)";
|
|
269
|
+
|
|
270
|
+
TEST(PolyhedronFaces, AConcaveFaceGivesTheRightVolume) {
|
|
271
|
+
Evaluated e = evaluateSrc(kLPrism);
|
|
272
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
273
|
+
ASSERT_TRUE(e.bodies[0].body.has_value());
|
|
274
|
+
// The L is 3 square units, extruded 1 high.
|
|
275
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 3.0, 1e-9);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
TEST(PolyhedronFaces, AConcaveFaceGivesTheRightSurfaceArea) {
|
|
279
|
+
Evaluated e = evaluateSrc(kLPrism);
|
|
280
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
281
|
+
// 2 x 3 for the ends, plus a perimeter of 8 x height 1.
|
|
282
|
+
EXPECT_NEAR(e.bodies[0].body->SurfaceArea(), 14.0, 1e-9);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
TEST(PolyhedronFaces, EachFaceBecomesExactlyNMinusTwoTriangles) {
|
|
286
|
+
Evaluated e = evaluateSrc(kLPrism);
|
|
287
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
288
|
+
// Two 6-gons (4 each) and six quads (2 each).
|
|
289
|
+
const manifold::MeshGL mesh = e.bodies[0].body->GetMeshGL();
|
|
290
|
+
EXPECT_EQ(mesh.triVerts.size() / 3, 4u + 4u + 6u * 2u);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// A convex face has to keep working, and a triangle must not be disturbed.
|
|
294
|
+
TEST(PolyhedronFaces, AFaceWithAnUnseeableCornerGivesTheRightVolume) {
|
|
295
|
+
Evaluated e = evaluateSrc(kUPrism);
|
|
296
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
297
|
+
ASSERT_TRUE(e.bodies[0].body.has_value());
|
|
298
|
+
// 3x3 less the 1x2 notch, one unit high.
|
|
299
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 7.0, 1e-9);
|
|
300
|
+
// Two ends of 7, plus a 16-long perimeter one unit high.
|
|
301
|
+
EXPECT_NEAR(e.bodies[0].body->SurfaceArea(), 30.0, 1e-9);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
TEST(PolyhedronFaces, AConvexFaceIsUnchanged) {
|
|
305
|
+
Evaluated e = evaluateSrc("cube(2);");
|
|
306
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
307
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 8.0, 1e-9);
|
|
308
|
+
EXPECT_NEAR(e.bodies[0].body->SurfaceArea(), 24.0, 1e-9);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
TEST(PolyhedronFaces, ADegenerateFaceDoesNotThrowOrHang) {
|
|
312
|
+
// All points collinear: no plane to project onto. It must fall through
|
|
313
|
+
// rather than spin in the ear-clipping loop.
|
|
314
|
+
Evaluated e = evaluateSrc(R"(
|
|
315
|
+
polyhedron(points=[[0,0,0],[1,0,0],[2,0,0],[0,0,1]],
|
|
316
|
+
faces=[[0,1,2],[0,2,3],[0,3,1],[1,3,2]]);
|
|
317
|
+
)");
|
|
318
|
+
EXPECT_EQ(e.bodies.size(), 1u);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// -- 2D minkowski ---------------------------------------------------------
|
|
322
|
+
|
|
323
|
+
// minkowski() used to drop 2D sections on the floor, silently: this
|
|
324
|
+
// produced no geometry at all where the reference produces the rounded
|
|
325
|
+
// square asked for. Manifold has no 2D Minkowski, so it is built from
|
|
326
|
+
// convex pieces and hulls -- see minkowski2d().
|
|
327
|
+
TEST(Minkowski2D, RoundsAConvexOutline) {
|
|
328
|
+
Evaluated e = evaluateSrc(
|
|
329
|
+
"linear_extrude(6) minkowski() { square([30,20], center=true); circle(4, $fn=24); }");
|
|
330
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
331
|
+
ASSERT_TRUE(e.bodies[0].body.has_value());
|
|
332
|
+
// A 30x20 rectangle grown by 4: the rectangle, four 4-wide sides, and
|
|
333
|
+
// the corners adding up to one circle. Times 6 high.
|
|
334
|
+
const double expected = (30.0 * 20.0 + 2 * 4 * (30 + 20) + M_PI * 16.0) * 6.0;
|
|
335
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), expected, expected * 0.01);
|
|
336
|
+
const manifold::Box box = e.bodies[0].body->BoundingBox();
|
|
337
|
+
EXPECT_NEAR(box.max.x - box.min.x, 38.0, 0.01);
|
|
338
|
+
EXPECT_NEAR(box.max.y - box.min.y, 28.0, 0.01);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// The case a hull cannot fake: a concave outline has to stay concave.
|
|
342
|
+
TEST(Minkowski2D, KeepsAConcaveOutlineConcave) {
|
|
343
|
+
Evaluated e = evaluateSrc(R"(
|
|
344
|
+
linear_extrude(4) minkowski() {
|
|
345
|
+
polygon([[0,0],[40,0],[40,25],[22,25],[22,12],[0,12]]);
|
|
346
|
+
circle(3, $fn=16);
|
|
347
|
+
}
|
|
348
|
+
)");
|
|
349
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
350
|
+
const manifold::Box box = e.bodies[0].body->BoundingBox();
|
|
351
|
+
EXPECT_NEAR(box.max.x - box.min.x, 46.0, 0.2);
|
|
352
|
+
EXPECT_NEAR(box.max.y - box.min.y, 31.0, 0.2);
|
|
353
|
+
// The notch has to still be a notch. A volume bound is too blunt to
|
|
354
|
+
// say so -- hulling the pieces rather than unioning them lands within
|
|
355
|
+
// 1% of the right answer here -- so probe the hole itself: the L is
|
|
356
|
+
// missing the region x 0..22, y 12..25, and rounding by 3 does not
|
|
357
|
+
// reach anywhere near the middle of it.
|
|
358
|
+
// The probe has to sit inside what a hull WOULD cover and outside the
|
|
359
|
+
// real shape, or it proves nothing: the hull runs from (0,12) to
|
|
360
|
+
// (22,25), and the rounded L stops at y=15 for any x below 22.
|
|
361
|
+
const manifold::Manifold probe =
|
|
362
|
+
manifold::Manifold::Cube({1.0, 1.0, 8.0}).Translate({17.0, 17.0, -2.0});
|
|
363
|
+
EXPECT_TRUE((*e.bodies[0].body ^ probe).IsEmpty()) << "the notch was filled in";
|
|
364
|
+
// ...while somewhere solid is genuinely solid.
|
|
365
|
+
const manifold::Manifold inside =
|
|
366
|
+
manifold::Manifold::Cube({2.0, 2.0, 8.0}).Translate({30.0, 5.0, -2.0});
|
|
367
|
+
EXPECT_FALSE((*e.bodies[0].body ^ inside).IsEmpty());
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
TEST(Minkowski2D, OneSectionIsHandedBackUnchanged) {
|
|
371
|
+
Evaluated e = evaluateSrc("linear_extrude(3) minkowski() { square([10,10]); }");
|
|
372
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
373
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 300.0, 1e-6);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
TEST(Minkowski2D, ThreeDStillWorksAndStillWins) {
|
|
377
|
+
Evaluated a = evaluateSrc("minkowski() { cube([30,20,6], center=true); sphere(3, $fn=12); }");
|
|
378
|
+
ASSERT_EQ(a.bodies.size(), 1u);
|
|
379
|
+
EXPECT_GT(a.bodies[0].body->Volume(), 30.0 * 20.0 * 6.0);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// The edge sweep is only valid when the shape being swept is convex and
|
|
383
|
+
// contains the origin, and when a third operand is folded rather than
|
|
384
|
+
// unioned in. Each of these fails, by a measurable amount, if the
|
|
385
|
+
// corresponding step is skipped -- so each is here on its own.
|
|
386
|
+
|
|
387
|
+
TEST(Minkowski2D, SweepsAConcaveSweeperByItsConvexPieces) {
|
|
388
|
+
// Hulling the sweeper instead of decomposing it computes A (+) hull(B)
|
|
389
|
+
// and comes out at 5092 against the reference's 4992.
|
|
390
|
+
Evaluated e = evaluateSrc(R"(
|
|
391
|
+
linear_extrude(4) minkowski() {
|
|
392
|
+
polygon([[0,0],[40,0],[40,25],[22,25],[22,12],[0,12]]);
|
|
393
|
+
polygon([[0,0],[8,0],[8,8],[5,8],[5,3],[0,3]]);
|
|
394
|
+
}
|
|
395
|
+
)");
|
|
396
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
397
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 4992.0, 5.0);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
TEST(Minkowski2D, ASweeperAwayFromTheOriginStillLandsInTheRightPlace) {
|
|
401
|
+
// The identity unions A itself, which only holds when the sweeper
|
|
402
|
+
// contains the origin. Without shifting it there and back, the result
|
|
403
|
+
// keeps an untranslated copy of A and starts at x=0 instead of x=16.
|
|
404
|
+
Evaluated e = evaluateSrc(R"(
|
|
405
|
+
linear_extrude(4) minkowski() {
|
|
406
|
+
polygon([[0,0],[40,0],[40,25],[22,25],[22,12],[0,12]]);
|
|
407
|
+
translate([20,0]) circle(4, $fn=24);
|
|
408
|
+
}
|
|
409
|
+
)");
|
|
410
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
411
|
+
const manifold::Box box = e.bodies[0].body->BoundingBox();
|
|
412
|
+
EXPECT_NEAR(box.min.x, 16.0, 0.2);
|
|
413
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 5120.47, 5.0);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
TEST(Minkowski2D, ThreeOperandsAreSummedInTurnNotUnioned) {
|
|
417
|
+
// A (+) B (+) C, not A (+) (B union C) -- which would give 3711.
|
|
418
|
+
Evaluated e = evaluateSrc(
|
|
419
|
+
"linear_extrude(4) minkowski() { square([30,20], center=true);"
|
|
420
|
+
" circle(3, $fn=16); square([6,2], center=true); }");
|
|
421
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
422
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 4670.21, 5.0);
|
|
423
|
+
const manifold::Box box = e.bodies[0].body->BoundingBox();
|
|
424
|
+
EXPECT_NEAR(box.max.x - box.min.x, 42.0, 0.2);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
TEST(Minkowski2D, AShapeWithAHoleHasBothItsContoursSwept) {
|
|
428
|
+
// Only the boundary of the shape being swept along is walked, so a
|
|
429
|
+
// hole is not a special case -- it is another contour.
|
|
430
|
+
Evaluated e = evaluateSrc(R"(
|
|
431
|
+
linear_extrude(3) minkowski() {
|
|
432
|
+
difference() { square([40,30], center=true); circle(8, $fn=24); }
|
|
433
|
+
circle(2, $fn=16);
|
|
434
|
+
}
|
|
435
|
+
)");
|
|
436
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
437
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 4141.98, 5.0);
|
|
438
|
+
// The hole shrinks by the sweep rather than filling in.
|
|
439
|
+
const manifold::Manifold probe =
|
|
440
|
+
manifold::Manifold::Cube({1.0, 1.0, 6.0}).Translate({-0.5, -0.5, -1.0});
|
|
441
|
+
EXPECT_TRUE((*e.bodies[0].body ^ probe).IsEmpty()) << "the hole was filled in";
|
|
442
|
+
}
|