openscad-cpp-evaluator 0.34.0__tar.gz → 0.36.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.34.0 → openscad_cpp_evaluator-0.36.0}/CLAUDE.md +37 -1
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/builtins.hpp +5 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/primitives_3d.cpp +241 -38
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/registry.cpp +4 -1
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/topology.cpp +62 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_booleans.cpp +79 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_primitives.cpp +126 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/uv.lock +1 -1
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/README.md +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/tools/cli/main.cpp +0 -0
|
@@ -561,6 +561,29 @@ grep for `ponytail:`.
|
|
|
561
561
|
color instead of preserving each part's own, matching real OpenSCAD's actual behavior once fixed
|
|
562
562
|
(cross-checked triangle-color-count-and-values against the Python reference directly, not just
|
|
563
563
|
“does it run”).
|
|
564
|
+
- `src/builtins/primitives_3d.cpp` — `sphere(style=)` is a BelfrySCAD extension naming the
|
|
565
|
+
tessellation, using BOSL2's five `spheroid()` style names and constructions: `"orig"` (the default,
|
|
566
|
+
and what OpenSCAD's own sphere() builds — rings offset half a step from the poles, no pole vertex),
|
|
567
|
+
`"aligned"` (a vertex at each pole, so an `$fn` divisible by 4 also lands vertices exactly on ±X
|
|
568
|
+
and ±Y), `"stagger"` (aligned with alternate rings rotated half a face), `"octa"` and `"icosa"`.
|
|
569
|
+
Only `"orig"` renders the same in real OpenSCAD.
|
|
570
|
+
|
|
571
|
+
`"octa"` is `Manifold::Sphere`, which already *is* a subdivided octahedron (`Shape::Octahedron`
|
|
572
|
+
then `Subdivide`). Same 146/288 topology as BOSL2's at `$fn=24`, ~0.05% apart in volume because
|
|
573
|
+
Manifold distributes the subdivision differently. `"icosa"` samples each icosahedral face against
|
|
574
|
+
its own corners rather than rotating copies of one sampled face the way BOSL2 does — the sampling
|
|
575
|
+
is affine in those corners, so it is the same points with far less machinery — and welds the
|
|
576
|
+
shared edges itself so the mesh reaches Manifold already manifold.
|
|
577
|
+
|
|
578
|
+
**BOSL2's VNF winding is the opposite of Manifold's**, so `buildAligned` emits every triangle with
|
|
579
|
+
its last two indices swapped. Without it the solid is inside-out, which `Manifold::Volume()`
|
|
580
|
+
reports as a NEGATIVE volume — and which comparing `|volume|` (as an STL-based check naturally
|
|
581
|
+
does) hides completely. Two of the tests assert a positive volume specifically to catch this.
|
|
582
|
+
|
|
583
|
+
Note when checking any of this against BOSL2: **BOSL2 shadows `sphere()`** with its own module, so
|
|
584
|
+
a file that includes BOSL2 to "compare against the reference" is comparing BOSL2 with itself. Call
|
|
585
|
+
`spheroid()` from a BOSL2-including file and `sphere()` from one without.
|
|
586
|
+
|
|
564
587
|
- `src/builtins/dxf_dim.cpp` — `dxf_dim()`/`dxf_cross()`, which read a measurement out of a DXF file
|
|
565
588
|
rather than out of the model. Ported from the reference's `io/dxfdim.cc` plus the DIMENSION/LINE
|
|
566
589
|
half of `io/DxfData.cc`: `dxf_dim()` returns the first matching DIMENSION's value by its type
|
|
@@ -591,7 +614,7 @@ grep for `ponytail:`.
|
|
|
591
614
|
All 25 module/shape combinations were diffed against OpenSCAD 2026.02.01 warning for warning, with
|
|
592
615
|
the surviving geometry compared by STL volume.
|
|
593
616
|
|
|
594
|
-
- `src/builtins/topology.cpp` — `hull()`/`minkowski()`/`fill()`. All
|
|
617
|
+
- `src/builtins/topology.cpp` — `hull()`/`minkowski()`/`minkowski_difference()`/`fill()`. All four splice their children transparently in
|
|
595
618
|
the CSG tree exactly like union/difference/intersection (resolve just evaluates them for the
|
|
596
619
|
side effect); `hull()` picks an all-3D (`Manifold::Hull`) or all-2D (`CrossSection::Hull`) hull of
|
|
597
620
|
the *foreground* (role-split) children, falling back to sections only when no child has a body;
|
|
@@ -604,6 +627,19 @@ grep for `ponytail:`.
|
|
|
604
627
|
which matters because two outers can overlap once the holes between them are gone. Ported from
|
|
605
628
|
the reference's `applyFill2D`, and checked shape-for-shape against OpenSCAD 2026.02.01 by
|
|
606
629
|
extruding and comparing STL volumes.
|
|
630
|
+
|
|
631
|
+
`minkowski_difference()` is erosion, and a deliberate BelfrySCAD extension: OpenSCAD has no such
|
|
632
|
+
module and no way to spell the operation in the language, since `minkowski()` only ever sums.
|
|
633
|
+
Reads the same way — first child is the body, every child after it is a tool eroded away in turn
|
|
634
|
+
(`Manifold::MinkowskiDifference`). 3D only, matching `minkowski()`'s own 3D-wins dispatch, since
|
|
635
|
+
Manifold has no CrossSection erosion and 2D already has one in `offset(r=-N)`.
|
|
636
|
+
|
|
637
|
+
Worth knowing why this is a module rather than an extension of `offset()`: a *positive* 3D offset
|
|
638
|
+
is already expressible as `minkowski() { X; sphere(r); }`, so `offset(r=N)` on a solid would have
|
|
639
|
+
been pure sugar. Erosion is the half that is genuinely unavailable, and naming it after the
|
|
640
|
+
operation beats borrowing a 2D name for it. Eroding a cube by a cube is exact (no curved tool to
|
|
641
|
+
tessellate), which is what the tests assert on; a sphere tool lands a little large, and that gap
|
|
642
|
+
is the sphere's tessellation rather than an error in the erosion.
|
|
607
643
|
- `src/builtins/extrude.cpp` — `linear_extrude()`/`rotate_extrude()`/`projection(cut=)`. All three
|
|
608
644
|
union every 2D child into one `CrossSection` via `toCrossSection` first. `rotate_extrude`'s
|
|
609
645
|
segment count can't be resolved until generate time (it depends on the merged children's bounds,
|
|
@@ -165,6 +165,11 @@ std::vector<ColoredBody> generateHull(Evaluator& ev, const CSGParams& params,
|
|
|
165
165
|
Value builtinDxfDim(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& node);
|
|
166
166
|
Value builtinDxfCross(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& node);
|
|
167
167
|
|
|
168
|
+
CSGParams resolveMinkowskiDifference(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
|
|
169
|
+
std::vector<ColoredBody> generateMinkowskiDifference(Evaluator& ev, const CSGParams& params,
|
|
170
|
+
const std::vector<std::unique_ptr<CSGNode>>& children,
|
|
171
|
+
const oscad::ASTNode& node);
|
|
172
|
+
|
|
168
173
|
CSGParams resolveFill(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
|
|
169
174
|
std::vector<ColoredBody> generateFill(Evaluator& ev, const CSGParams& params,
|
|
170
175
|
const std::vector<std::unique_ptr<CSGNode>>& children,
|
{openscad_cpp_evaluator-0.34.0 → openscad_cpp_evaluator-0.36.0}/src/builtins/primitives_3d.cpp
RENAMED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
#include "openscad_cpp_evaluator/segments.hpp"
|
|
6
6
|
|
|
7
7
|
#include <algorithm>
|
|
8
|
+
#include <array>
|
|
8
9
|
#include <cmath>
|
|
9
10
|
#include <cstdint>
|
|
10
11
|
#include <map>
|
|
11
12
|
#include <numbers>
|
|
13
|
+
#include <set>
|
|
12
14
|
|
|
13
15
|
namespace oscadeval {
|
|
14
16
|
|
|
@@ -100,22 +102,48 @@ std::vector<ColoredBody> generateCube(Evaluator& ev, const CSGParams& params, co
|
|
|
100
102
|
// _resolve_sphere/_generate_sphere exactly, including the "stacks =
|
|
101
103
|
// max(2, ceil(n/2))" ring count and the fan-cap winding.
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
// sphere(r|d, style) -- `style` is a BelfrySCAD extension naming the
|
|
106
|
+
// tessellation, with the same five names and the same constructions as
|
|
107
|
+
// BOSL2's spheroid():
|
|
108
|
+
//
|
|
109
|
+
// "orig" stacked rings offset half a step from the poles, no pole
|
|
110
|
+
// vertex. What OpenSCAD's own sphere() builds, hence the name,
|
|
111
|
+
// and still the default here.
|
|
112
|
+
// "aligned" a vertex at each pole and rings on the latitudes between, so
|
|
113
|
+
// an $fn divisible by 4 puts vertices exactly on +-X and +-Y.
|
|
114
|
+
// "stagger" "aligned" with alternate rings rotated half a face, giving
|
|
115
|
+
// triangles that alternate direction rather than stacking.
|
|
116
|
+
// "octa" a subdivided octahedron projected onto the sphere.
|
|
117
|
+
// "icosa" a subdivided icosahedron projected onto the sphere -- the most
|
|
118
|
+
// uniform of the five.
|
|
119
|
+
//
|
|
120
|
+
// Only "orig" matches real OpenSCAD; the rest are additions, so a script
|
|
121
|
+
// using them will not render the same shape elsewhere.
|
|
113
122
|
|
|
114
|
-
|
|
115
|
-
const int stacks = std::max(2, static_cast<int>(std::ceil(n / 2.0)));
|
|
116
|
-
const double step = std::numbers::pi / stacks;
|
|
123
|
+
namespace {
|
|
117
124
|
|
|
118
|
-
|
|
125
|
+
// BOSL2's spherical_to_xyz(r, theta, phi): theta around Z from +X, phi down
|
|
126
|
+
// from +Z.
|
|
127
|
+
std::array<double, 3> sphericalToXyz(double r, double thetaDeg, double phiDeg) {
|
|
128
|
+
const double th = thetaDeg * std::numbers::pi / 180.0;
|
|
129
|
+
const double ph = phiDeg * std::numbers::pi / 180.0;
|
|
130
|
+
return {r * std::sin(ph) * std::cos(th), r * std::sin(ph) * std::sin(th), r * std::cos(ph)};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
struct SphereMesh {
|
|
134
|
+
std::vector<double> verts; // flat x,y,z
|
|
135
|
+
std::vector<int> tris;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
void pushVert(SphereMesh& m, const std::array<double, 3>& p) {
|
|
139
|
+
m.verts.insert(m.verts.end(), {p[0], p[1], p[2]});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// "orig": rings at (i+0.5) steps, no pole vertices, capped by a fan at each
|
|
143
|
+
// end. Unchanged from the original implementation.
|
|
144
|
+
SphereMesh buildOrig(double r, int n, int stacks) {
|
|
145
|
+
SphereMesh m;
|
|
146
|
+
const double step = std::numbers::pi / stacks;
|
|
119
147
|
std::vector<std::vector<int>> rings(static_cast<size_t>(stacks));
|
|
120
148
|
for (int s = 0; s < stacks; ++s) {
|
|
121
149
|
const double lat = -std::numbers::pi / 2.0 + (s + 0.5) * step;
|
|
@@ -123,44 +151,214 @@ CSGParams resolveSphere(Evaluator& ev, const oscad::ModularCall& node, EvalConte
|
|
|
123
151
|
const double z = r * std::sin(lat);
|
|
124
152
|
for (int seg = 0; seg < n; ++seg) {
|
|
125
153
|
const double angle = 2.0 * std::numbers::pi * seg / n;
|
|
126
|
-
rings[static_cast<size_t>(s)].push_back(static_cast<int>(verts.size() / 3));
|
|
127
|
-
|
|
128
|
-
verts.push_back(ringR * std::sin(angle));
|
|
129
|
-
verts.push_back(z);
|
|
154
|
+
rings[static_cast<size_t>(s)].push_back(static_cast<int>(m.verts.size() / 3));
|
|
155
|
+
pushVert(m, {ringR * std::cos(angle), ringR * std::sin(angle), z});
|
|
130
156
|
}
|
|
131
157
|
}
|
|
132
|
-
|
|
133
|
-
std::vector<int> tris;
|
|
134
158
|
const auto& bot = rings.front();
|
|
135
|
-
for (int i = 1; i < n - 1; ++i) {
|
|
136
|
-
tris.insert(tris.end(), {bot[0], bot[static_cast<size_t>(i) + 1], bot[static_cast<size_t>(i)]});
|
|
137
|
-
}
|
|
159
|
+
for (int i = 1; i < n - 1; ++i) m.tris.insert(m.tris.end(), {bot[0], bot[size_t(i) + 1], bot[size_t(i)]});
|
|
138
160
|
for (int s = 0; s < stacks - 1; ++s) {
|
|
139
|
-
const auto& lo = rings[
|
|
140
|
-
const auto& hi = rings[
|
|
161
|
+
const auto& lo = rings[size_t(s)];
|
|
162
|
+
const auto& hi = rings[size_t(s) + 1];
|
|
141
163
|
for (int seg = 0; seg < n; ++seg) {
|
|
142
|
-
const int a = lo[
|
|
143
|
-
const int c = hi[
|
|
144
|
-
tris.insert(tris.end(), {a, b, d});
|
|
145
|
-
tris.insert(tris.end(), {a, d, c});
|
|
164
|
+
const int a = lo[size_t(seg)], b = lo[size_t((seg + 1) % n)];
|
|
165
|
+
const int c = hi[size_t(seg)], d = hi[size_t((seg + 1) % n)];
|
|
166
|
+
m.tris.insert(m.tris.end(), {a, b, d});
|
|
167
|
+
m.tris.insert(m.tris.end(), {a, d, c});
|
|
146
168
|
}
|
|
147
169
|
}
|
|
148
170
|
const auto& top = rings.back();
|
|
149
|
-
for (int i = 1; i < n - 1; ++i) {
|
|
150
|
-
|
|
171
|
+
for (int i = 1; i < n - 1; ++i) m.tris.insert(m.tris.end(), {top[0], top[size_t(i)], top[size_t(i) + 1]});
|
|
172
|
+
return m;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// "aligned"/"stagger": pole, vsides-1 rings, pole. Vertex order and face
|
|
176
|
+
// indices follow BOSL2's spheroid() exactly so the two agree triangle for
|
|
177
|
+
// triangle, not merely in shape.
|
|
178
|
+
SphereMesh buildAligned(double r, int hsides, int vsides, bool stagger) {
|
|
179
|
+
SphereMesh m;
|
|
180
|
+
pushVert(m, sphericalToXyz(r, 0, 0)); // north pole, index 0
|
|
181
|
+
for (int i = 1; i <= vsides - 1; ++i) {
|
|
182
|
+
const double phi = i * 180.0 / vsides;
|
|
183
|
+
for (int j = 0; j < hsides; ++j) {
|
|
184
|
+
const double theta = (j + ((stagger && i % 2 != 0) ? 0.5 : 0.0)) * 360.0 / hsides;
|
|
185
|
+
pushVert(m, sphericalToXyz(r, theta, phi));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
pushVert(m, sphericalToXyz(r, 0, 180)); // south pole, last index
|
|
189
|
+
const int lv = static_cast<int>(m.verts.size() / 3);
|
|
190
|
+
|
|
191
|
+
// BOSL2's VNF winding is the opposite of what Manifold wants, so every
|
|
192
|
+
// triangle below is emitted with its last two indices swapped. Without
|
|
193
|
+
// that the solid comes out inside-out -- and invisibly so if you only
|
|
194
|
+
// ever check |volume|, which is how this first went unnoticed.
|
|
195
|
+
const auto tri = [&m](int a, int b, int c) { m.tris.insert(m.tris.end(), {a, c, b}); };
|
|
196
|
+
|
|
197
|
+
for (int i = 0; i < hsides; ++i) {
|
|
198
|
+
const int b2 = lv - 2 - hsides;
|
|
199
|
+
tri(i + 1, 0, ((i + 1) % hsides) + 1);
|
|
200
|
+
tri(lv - 1, b2 + i + 1, b2 + ((i + 1) % hsides) + 1);
|
|
151
201
|
}
|
|
202
|
+
for (int i = 0; i <= vsides - 3; ++i) {
|
|
203
|
+
const int base = 1 + hsides * i;
|
|
204
|
+
for (int j = 0; j < hsides; ++j) {
|
|
205
|
+
if (stagger && i % 2 != 0) {
|
|
206
|
+
tri(base + j, base + hsides + j % hsides, base + hsides + (j + hsides - 1) % hsides);
|
|
207
|
+
tri(base + j, base + (j + 1) % hsides, base + hsides + j);
|
|
208
|
+
} else {
|
|
209
|
+
tri(base + j, base + (j + 1) % hsides, base + hsides + (j + 1) % hsides);
|
|
210
|
+
tri(base + j, base + hsides + (j + 1) % hsides, base + hsides + j);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return m;
|
|
215
|
+
}
|
|
152
216
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
217
|
+
// "icosa": subdivide every icosahedral face into a triangular grid and push
|
|
218
|
+
// each sample out to the sphere. BOSL2 subsamples one face and rotates
|
|
219
|
+
// copies onto the rest; sampling each face against its own corners is the
|
|
220
|
+
// same points (the sampling is affine in those corners) with far less
|
|
221
|
+
// machinery. Coincident vertices along shared edges are welded here rather
|
|
222
|
+
// than left for Manifold, so the mesh arrives already manifold.
|
|
223
|
+
SphereMesh buildIcosa(double r, int hsides) {
|
|
224
|
+
const double phi = (1.0 + std::sqrt(5.0)) / 2.0;
|
|
225
|
+
std::vector<std::array<double, 3>> ico;
|
|
226
|
+
for (int i : {-1, 1}) {
|
|
227
|
+
for (int j : {-1, 1}) {
|
|
228
|
+
ico.push_back({0.0, double(i), double(j) * phi});
|
|
229
|
+
ico.push_back({double(i), double(j) * phi, 0.0});
|
|
230
|
+
ico.push_back({double(j) * phi, 0.0, double(i)});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Hull faces by brute force: a triple is a face when every other vertex
|
|
234
|
+
// lies on one side of its plane. 12 vertices, so 220 triples.
|
|
235
|
+
std::vector<std::array<int, 3>> faces;
|
|
236
|
+
const int nv = static_cast<int>(ico.size());
|
|
237
|
+
for (int a = 0; a < nv; ++a)
|
|
238
|
+
for (int b = a + 1; b < nv; ++b)
|
|
239
|
+
for (int c = b + 1; c < nv; ++c) {
|
|
240
|
+
const std::array<double, 3> u{ico[b][0] - ico[a][0], ico[b][1] - ico[a][1], ico[b][2] - ico[a][2]};
|
|
241
|
+
const std::array<double, 3> v{ico[c][0] - ico[a][0], ico[c][1] - ico[a][1], ico[c][2] - ico[a][2]};
|
|
242
|
+
const std::array<double, 3> nrm{u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2],
|
|
243
|
+
u[0] * v[1] - u[1] * v[0]};
|
|
244
|
+
int pos = 0, neg = 0;
|
|
245
|
+
for (int k = 0; k < nv; ++k) {
|
|
246
|
+
if (k == a || k == b || k == c) continue;
|
|
247
|
+
const double d = nrm[0] * (ico[k][0] - ico[a][0]) + nrm[1] * (ico[k][1] - ico[a][1]) +
|
|
248
|
+
nrm[2] * (ico[k][2] - ico[a][2]);
|
|
249
|
+
if (d > 1e-9) ++pos;
|
|
250
|
+
if (d < -1e-9) ++neg;
|
|
251
|
+
}
|
|
252
|
+
if (pos && neg) continue;
|
|
253
|
+
// Orient outward: the normal must point away from the centre.
|
|
254
|
+
if (neg == 0) faces.push_back({a, c, b});
|
|
255
|
+
else faces.push_back({a, b, c});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const int steps = std::max(1, static_cast<int>(std::lround(std::max(5, hsides) / 5.0)));
|
|
259
|
+
const int N = steps - 1; // BOSL2's N; the grid has N+2 rows
|
|
260
|
+
|
|
261
|
+
SphereMesh m;
|
|
262
|
+
std::map<std::array<long long, 3>, int> weld;
|
|
263
|
+
const auto add = [&](const std::array<double, 3>& p) {
|
|
264
|
+
const double len = std::sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * p[2]);
|
|
265
|
+
const std::array<double, 3> u{r * p[0] / len, r * p[1] / len, r * p[2] / len};
|
|
266
|
+
const std::array<long long, 3> key{std::llround(u[0] * 1e9), std::llround(u[1] * 1e9),
|
|
267
|
+
std::llround(u[2] * 1e9)};
|
|
268
|
+
auto it = weld.find(key);
|
|
269
|
+
if (it != weld.end()) return it->second;
|
|
270
|
+
const int idx = static_cast<int>(m.verts.size() / 3);
|
|
271
|
+
pushVert(m, u);
|
|
272
|
+
weld.emplace(key, idx);
|
|
273
|
+
return idx;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
for (const std::array<int, 3>& f : faces) {
|
|
277
|
+
const std::array<double, 3>& p0 = ico[f[0]];
|
|
278
|
+
const std::array<double, 3>& p1 = ico[f[1]];
|
|
279
|
+
const std::array<double, 3>& p2 = ico[f[2]];
|
|
280
|
+
// Row i has N+2-i samples, mirroring _subsample_triangle.
|
|
281
|
+
std::vector<std::vector<int>> grid;
|
|
282
|
+
for (int i = 0; i <= N + 1; ++i) {
|
|
283
|
+
std::vector<int> row;
|
|
284
|
+
for (int j = 0; j <= N + 1 - i; ++j) {
|
|
285
|
+
const double a = double(i) / (N + 1), b = double(j) / (N + 1);
|
|
286
|
+
row.push_back(add({p0[0] + (p1[0] - p0[0]) * a + (p2[0] - p0[0]) * b,
|
|
287
|
+
p0[1] + (p1[1] - p0[1]) * a + (p2[1] - p0[1]) * b,
|
|
288
|
+
p0[2] + (p1[2] - p0[2]) * a + (p2[2] - p0[2]) * b}));
|
|
289
|
+
}
|
|
290
|
+
grid.push_back(std::move(row));
|
|
291
|
+
}
|
|
292
|
+
for (int i = 0; i <= N; ++i) {
|
|
293
|
+
for (int j = 0; j <= N - i; ++j) {
|
|
294
|
+
m.tris.insert(m.tris.end(), {grid[i][j], grid[i + 1][j], grid[i][j + 1]});
|
|
295
|
+
if (j < N - i) {
|
|
296
|
+
m.tris.insert(m.tris.end(), {grid[i + 1][j], grid[i + 1][j + 1], grid[i][j + 1]});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return m;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
} // namespace
|
|
305
|
+
|
|
306
|
+
CSGParams resolveSphere(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
|
|
307
|
+
auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
|
|
308
|
+
Value dArg = getArg(args, std::nullopt, "d", Value{});
|
|
309
|
+
double r;
|
|
310
|
+
if (!isUndef(dArg)) {
|
|
311
|
+
r = toDoubleLenient(dArg) / 2.0;
|
|
312
|
+
} else {
|
|
313
|
+
Value rArg = getArg(args, 0, "r", Value{});
|
|
314
|
+
r = isUndef(rArg) ? 1.0 : toDoubleLenient(rArg);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const Value styleArg = getArg(args, std::nullopt, "style", Value{});
|
|
318
|
+
std::string style = "orig";
|
|
319
|
+
if (const std::string* sv = std::get_if<std::string>(&styleArg)) {
|
|
320
|
+
static const std::set<std::string> known{"orig", "aligned", "stagger", "octa", "icosa"};
|
|
321
|
+
if (known.count(*sv)) {
|
|
322
|
+
style = *sv;
|
|
323
|
+
} else {
|
|
324
|
+
ev.warn("sphere: unknown style \"" + *sv + "\"; expected one of orig, aligned, stagger, octa, icosa",
|
|
325
|
+
&node.position());
|
|
326
|
+
}
|
|
327
|
+
} else if (!isUndef(styleArg)) {
|
|
328
|
+
ev.warn("sphere: style must be a string", &node.position());
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const int hsides = fnSegmentsFromCtx(effCtx, r);
|
|
332
|
+
const int vsides = std::max(2, static_cast<int>(std::ceil(hsides / 2.0)));
|
|
159
333
|
|
|
160
334
|
CSGParams params;
|
|
335
|
+
params["style"] = Value{style};
|
|
336
|
+
params["r"] = Value{r};
|
|
337
|
+
params["segs"] = Value{static_cast<double>(hsides)};
|
|
338
|
+
params["color"] = colorToValue(effCtx.color);
|
|
339
|
+
|
|
340
|
+
if (style == "octa") {
|
|
341
|
+
// Manifold::Sphere IS a subdivided octahedron (Shape::Octahedron,
|
|
342
|
+
// then Subdivide), so this needs no mesh of our own -- generate
|
|
343
|
+
// calls it directly.
|
|
344
|
+
params["verts"] = Value{std::make_shared<const ValueList>(ValueList{})};
|
|
345
|
+
params["tris"] = Value{std::make_shared<const ValueList>(ValueList{})};
|
|
346
|
+
return params;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const SphereMesh m = style == "aligned" ? buildAligned(r, hsides, vsides, false)
|
|
350
|
+
: style == "stagger" ? buildAligned(r, hsides, vsides, true)
|
|
351
|
+
: style == "icosa" ? buildIcosa(r, hsides)
|
|
352
|
+
: buildOrig(r, hsides, vsides);
|
|
353
|
+
|
|
354
|
+
std::vector<Value> vertsValues;
|
|
355
|
+
vertsValues.reserve(m.verts.size());
|
|
356
|
+
for (double v : m.verts) vertsValues.push_back(Value{v});
|
|
357
|
+
std::vector<Value> trisValues;
|
|
358
|
+
trisValues.reserve(m.tris.size());
|
|
359
|
+
for (int t : m.tris) trisValues.push_back(Value{static_cast<double>(t)});
|
|
161
360
|
params["verts"] = Value{std::make_shared<const ValueList>(ValueList{std::move(vertsValues)})};
|
|
162
361
|
params["tris"] = Value{std::make_shared<const ValueList>(ValueList{std::move(trisValues)})};
|
|
163
|
-
params["color"] = colorToValue(effCtx.color);
|
|
164
362
|
return params;
|
|
165
363
|
}
|
|
166
364
|
|
|
@@ -174,6 +372,11 @@ std::vector<ColoredBody> generateSphere(Evaluator& ev, const CSGParams& params,
|
|
|
174
372
|
for (const Value& t : std::get<ListPtr>(params.at("tris"))->items) {
|
|
175
373
|
mesh.triVerts.push_back(static_cast<uint64_t>(std::get<double>(t)));
|
|
176
374
|
}
|
|
375
|
+
if (std::get<std::string>(params.at("style")) == "octa") {
|
|
376
|
+
manifold::Manifold octa = manifold::Manifold::Sphere(
|
|
377
|
+
std::get<double>(params.at("r")), static_cast<int>(std::get<double>(params.at("segs"))));
|
|
378
|
+
return {ev.tagGenerated(std::move(octa), node, params.at("color"))};
|
|
379
|
+
}
|
|
177
380
|
manifold::Manifold body(mesh);
|
|
178
381
|
return {ev.tagGenerated(std::move(body), node, params.at("color"))};
|
|
179
382
|
}
|
|
@@ -35,6 +35,7 @@ const std::unordered_map<std::string_view, ResolveFn>& resolveDispatch() {
|
|
|
35
35
|
{"projection", &resolveProjection},
|
|
36
36
|
{"roof", &resolveRoof},
|
|
37
37
|
{"fill", &resolveFill},
|
|
38
|
+
{"minkowski_difference", &resolveMinkowskiDifference},
|
|
38
39
|
{"offset", &resolveOffset},
|
|
39
40
|
{"surface", &resolveSurface},
|
|
40
41
|
{"text", &resolveText},
|
|
@@ -74,6 +75,7 @@ const std::unordered_map<std::string_view, GenerateFn>& generateDispatch() {
|
|
|
74
75
|
{"projection", &generateProjection},
|
|
75
76
|
{"roof", &generateRoof},
|
|
76
77
|
{"fill", &generateFill},
|
|
78
|
+
{"minkowski_difference", &generateMinkowskiDifference},
|
|
77
79
|
{"offset", &generateOffset},
|
|
78
80
|
{"surface", &generateSurface},
|
|
79
81
|
{"text", &generateText},
|
|
@@ -90,7 +92,7 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
|
|
|
90
92
|
static const std::unordered_map<std::string, std::vector<std::string>> table = {
|
|
91
93
|
// -- builtin modules ---------------------------------------------
|
|
92
94
|
{"cube", {"size", "center"}},
|
|
93
|
-
{"sphere", {"r", "d"}},
|
|
95
|
+
{"sphere", {"r", "d", "style"}},
|
|
94
96
|
{"cylinder", {"h", "r1", "r2", "center", "r", "d", "d1", "d2"}},
|
|
95
97
|
{"polyhedron", {"points", "faces", "convexity", "triangles"}},
|
|
96
98
|
{"square", {"size", "center"}},
|
|
@@ -119,6 +121,7 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
|
|
|
119
121
|
{"projection", {"cut", "convexity"}},
|
|
120
122
|
{"roof", {"method", "convexity"}},
|
|
121
123
|
{"fill", {}},
|
|
124
|
+
{"minkowski_difference", {}},
|
|
122
125
|
{"offset", {"r", "delta", "chamfer"}},
|
|
123
126
|
{"surface", {"file", "center", "convexity", "invert"}},
|
|
124
127
|
{"text", {"text", "size", "font", "direction", "language", "script", "halign", "valign", "spacing"}},
|
|
@@ -313,4 +313,66 @@ std::vector<ColoredBody> generateMinkowski(Evaluator& ev, const CSGParams&, cons
|
|
|
313
313
|
return out;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
// minkowski_difference() -- erosion, the operation `minkowski()` has no
|
|
317
|
+
// inverse for. A BelfrySCAD extension: OpenSCAD has no equivalent module and
|
|
318
|
+
// no way to express it in the language, since minkowski() only ever sums.
|
|
319
|
+
// Shrinking a part by a clearance, or hollowing one to a wall thickness,
|
|
320
|
+
// otherwise needs hand-built difference() scaffolding that is not the same
|
|
321
|
+
// operation.
|
|
322
|
+
//
|
|
323
|
+
// Reads like minkowski(): the first child is the body being eroded, and
|
|
324
|
+
// every child after it is a tool eroded away from it in turn. 3D only, same
|
|
325
|
+
// as minkowski()'s own 3D path -- Manifold has no CrossSection erosion, and
|
|
326
|
+
// 2D already has one in offset(r=-N).
|
|
327
|
+
|
|
328
|
+
CSGParams resolveMinkowskiDifference(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
|
|
329
|
+
auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
|
|
330
|
+
(void)args;
|
|
331
|
+
ev.evalChildren(node.children, effCtx);
|
|
332
|
+
return CSGParams{};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
std::vector<ColoredBody> generateMinkowskiDifference(Evaluator& ev, const CSGParams&,
|
|
336
|
+
const std::vector<std::unique_ptr<CSGNode>>& children,
|
|
337
|
+
const oscad::ASTNode& node) {
|
|
338
|
+
const std::vector<ColoredBody> bodies = flattenCsgTree(children);
|
|
339
|
+
const RoleSplit split = splitByRole(bodies);
|
|
340
|
+
|
|
341
|
+
std::vector<const ColoredBody*> bodies3d;
|
|
342
|
+
for (const ColoredBody& c : split.foreground) {
|
|
343
|
+
if (c.body) bodies3d.push_back(&c);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
std::vector<ColoredBody> passthrough;
|
|
347
|
+
passthrough.insert(passthrough.end(), split.background.begin(), split.background.end());
|
|
348
|
+
passthrough.insert(passthrough.end(), split.highlight.begin(), split.highlight.end());
|
|
349
|
+
passthrough.insert(passthrough.end(), split.showOnly.begin(), split.showOnly.end());
|
|
350
|
+
passthrough.insert(passthrough.end(), split.displayOnly.begin(), split.displayOnly.end());
|
|
351
|
+
|
|
352
|
+
if (bodies3d.empty()) return passthrough;
|
|
353
|
+
if (bodies3d.size() == 1) {
|
|
354
|
+
// Nothing to erode with, so nothing happens -- the same no-op
|
|
355
|
+
// minkowski() gives for a single child.
|
|
356
|
+
std::vector<ColoredBody> result = {*bodies3d.front()};
|
|
357
|
+
result.insert(result.end(), passthrough.begin(), passthrough.end());
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
manifold::Manifold result = *bodies3d.front()->body;
|
|
362
|
+
for (size_t i = 1; i < bodies3d.size(); ++i) result = result.MinkowskiDifference(*bodies3d[i]->body);
|
|
363
|
+
if (result.Status() != manifold::Manifold::Error::NoError) {
|
|
364
|
+
ev.warn("minkowski_difference: result is not manifold", &node.position());
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
ColoredBody cb;
|
|
368
|
+
cb.body = std::move(result);
|
|
369
|
+
cb.color = bodies3d.front()->color;
|
|
370
|
+
// Per-triangle colour cannot survive an erosion: the surface it was
|
|
371
|
+
// indexed against is gone.
|
|
372
|
+
cb.triColors.reset();
|
|
373
|
+
std::vector<ColoredBody> out = {std::move(cb)};
|
|
374
|
+
out.insert(out.end(), passthrough.begin(), passthrough.end());
|
|
375
|
+
return out;
|
|
376
|
+
}
|
|
377
|
+
|
|
316
378
|
} // namespace oscadeval
|
|
@@ -620,3 +620,82 @@ TEST(MixedDimensions, UniformChildrenAreUntouched) {
|
|
|
620
620
|
ASSERT_EQ(e.bodies.size(), 1u);
|
|
621
621
|
EXPECT_NEAR(e.bodies[0].body->Volume(), 2.0, 1e-9);
|
|
622
622
|
}
|
|
623
|
+
|
|
624
|
+
// -- minkowski_difference() -----------------------------------------------
|
|
625
|
+
//
|
|
626
|
+
// Erosion: the operation minkowski() has no inverse for. A BelfrySCAD
|
|
627
|
+
// extension, because OpenSCAD has no equivalent module and no way to spell
|
|
628
|
+
// it in the language -- minkowski() only ever sums.
|
|
629
|
+
//
|
|
630
|
+
// Eroding a cube by a cube is exact (no curved tool to tessellate), so those
|
|
631
|
+
// expectations are equalities rather than tolerances.
|
|
632
|
+
|
|
633
|
+
TEST(MinkowskiDifference, ErodesACubeExactly) {
|
|
634
|
+
// A 10-cube eroded by a 2-cube is an 8-cube: every face moves in by the
|
|
635
|
+
// tool's half-width.
|
|
636
|
+
Evaluated e = evalSrc("minkowski_difference() { cube(10, center=true); cube(2, center=true); }");
|
|
637
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
638
|
+
ASSERT_TRUE(e.bodies[0].body.has_value());
|
|
639
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 512.0, 1e-6);
|
|
640
|
+
const manifold::Box bb = e.bodies[0].body->BoundingBox();
|
|
641
|
+
EXPECT_NEAR(bb.min.x, -4.0, 1e-9);
|
|
642
|
+
EXPECT_NEAR(bb.max.x, 4.0, 1e-9);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
TEST(MinkowskiDifference, ErodesByASphereToWithinTessellation) {
|
|
646
|
+
// Same 8-cube, but the tool is a polyhedral sphere whose inradius is a
|
|
647
|
+
// touch under 1, so the result is a touch larger. That gap IS the
|
|
648
|
+
// tessellation and shrinks with $fn -- it is not an error in the erosion.
|
|
649
|
+
Evaluated e = evalSrc("minkowski_difference() { cube(10, center=true); sphere(1, $fn=64); }");
|
|
650
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
651
|
+
ASSERT_TRUE(e.bodies[0].body.has_value());
|
|
652
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 512.0, 1.0);
|
|
653
|
+
EXPECT_GT(e.bodies[0].body->Volume(), 512.0);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
TEST(MinkowskiDifference, ShrinksANonConvexBodyOnEverySide) {
|
|
657
|
+
// The case that motivates the module: a plate with holes, shrunk by a
|
|
658
|
+
// clearance. The bounding box must come in by the tool's radius all
|
|
659
|
+
// round -- 20x20x4 becomes 18x18x2.
|
|
660
|
+
Evaluated e = evalSrc(
|
|
661
|
+
"minkowski_difference() {"
|
|
662
|
+
" difference() { cube([20,20,4], center=true);"
|
|
663
|
+
" for (x=[-6,0,6]) translate([x,0,0]) cylinder(h=6,r=2.5,center=true,$fn=24); }"
|
|
664
|
+
" sphere(1, $fn=24); }");
|
|
665
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
666
|
+
ASSERT_TRUE(e.bodies[0].body.has_value());
|
|
667
|
+
const manifold::Box bb = e.bodies[0].body->BoundingBox();
|
|
668
|
+
EXPECT_NEAR(bb.max.x, 9.0, 0.05);
|
|
669
|
+
EXPECT_NEAR(bb.max.y, 9.0, 0.05);
|
|
670
|
+
EXPECT_NEAR(bb.max.z, 1.0, 0.05);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
TEST(MinkowskiDifference, SingleChildIsANoOp) {
|
|
674
|
+
// Nothing to erode with, so nothing happens -- the same shape
|
|
675
|
+
// minkowski() gives a lone child back.
|
|
676
|
+
Evaluated e = evalSrc("minkowski_difference() { cube(10, center=true); }");
|
|
677
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
678
|
+
ASSERT_TRUE(e.bodies[0].body.has_value());
|
|
679
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 1000.0, 1e-9);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
TEST(MinkowskiDifference, NoChildrenIsEmpty) {
|
|
683
|
+
Evaluated e = evalSrc("minkowski_difference();");
|
|
684
|
+
EXPECT_TRUE(e.bodies.empty());
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
TEST(MinkowskiDifference, ErodesSuccessivelyByEveryToolAfterTheFirst) {
|
|
688
|
+
// Two 2-cube tools take 1 off each side twice: 10 -> 8 -> 6.
|
|
689
|
+
Evaluated e = evalSrc(
|
|
690
|
+
"minkowski_difference() { cube(10, center=true); cube(2, center=true); cube(2, center=true); }");
|
|
691
|
+
ASSERT_EQ(e.bodies.size(), 1u);
|
|
692
|
+
ASSERT_TRUE(e.bodies[0].body.has_value());
|
|
693
|
+
EXPECT_NEAR(e.bodies[0].body->Volume(), 216.0, 1e-6);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
TEST(MinkowskiDifference, TwoDeeChildrenAreIgnoredLikeMinkowskis) {
|
|
697
|
+
// 3D only, matching minkowski()'s own 3D-wins dispatch. 2D erosion
|
|
698
|
+
// already exists as offset(r=-N).
|
|
699
|
+
Evaluated e = evalSrc("minkowski_difference() { square(10); square(2); }");
|
|
700
|
+
EXPECT_TRUE(e.bodies.empty());
|
|
701
|
+
}
|