openscad-cpp-evaluator 0.50.0__tar.gz → 0.51.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.50.0 → openscad_cpp_evaluator-0.51.0}/CLAUDE.md +29 -2
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/function_builtins.cpp +13 -3
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/topology.cpp +351 -48
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_booleans.cpp +139 -3
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_function_builtins.cpp +14 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/README.md +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_feature_detection.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/uv.lock +0 -0
|
@@ -461,8 +461,17 @@ grep for `ponytail:`.
|
|
|
461
461
|
and both read as `undef` in OpenSCAD, so a guard written against them works in both.
|
|
462
462
|
|
|
463
463
|
- **`levelset(field, bounds, isovalue)`** (`src/builtins/topology.cpp`, feature name `levelset`) —
|
|
464
|
-
a solid
|
|
465
|
-
|
|
464
|
+
a solid (or a section) from an implicit surface. **2D or 3D is decided by `bounds`**, not guessed
|
|
465
|
+
from the field: a 2-vector corner means a section, a 3-vector means a solid. `field` is an array
|
|
466
|
+
(`field[i][j]` or `field[i][j][k]`, i,j,k → x,y,z) or a `function(x,y)`/`function(x,y,z)`.
|
|
467
|
+
The array is **not parsed until `bounds` has been read** — parsing it as 3D up front rejected
|
|
468
|
+
every 2D grid before the 2D path could run.
|
|
469
|
+
**`isovalue` is a band**: a scalar `v` is `[-INF, v]` ("at or below", the distance-field
|
|
470
|
+
reading), a range means *between*, and `[lo, INF]` is BOSL2's "at or above" idiom — so code
|
|
471
|
+
forwarding BOSL2's own isovalue gets BOSL2's semantics with no translation and no `invert`.
|
|
472
|
+
One `LevelSet` pass either way; a bounded range *could* be
|
|
473
|
+
`difference(levelset(hi), levelset(lo))` but that meshes twice and then booleans.
|
|
474
|
+
3D wraps `Manifold::LevelSet` with a C++ sampling lambda; no new Manifold API.
|
|
466
475
|
**The field is a grid OR a `function(x,y,z)`, and the two trade off against each other** —
|
|
467
476
|
neither is simply better:
|
|
468
477
|
|
|
@@ -492,6 +501,24 @@ grep for `ponytail:`.
|
|
|
492
501
|
same reason.
|
|
493
502
|
Calling the field function from the generate pass has no live `EvalContext`, so a root is built
|
|
494
503
|
from the closure's own scope — `$`-variables sit at their defaults inside it.
|
|
504
|
+
**An explicitly-undef optional argument counts as ABSENT**, not as a bad one — the same rule in
|
|
505
|
+
`linear_solve` (`b`) and `levelset` (`isovalue`). BOSL2 reaches a builtin it also defines by
|
|
506
|
+
wrapping it in `builtins.scad`, which `use <>`s nothing, so the names there bind to the builtins:
|
|
507
|
+
`function _linear_solve(A, b) = linear_solve(A, b);`. That wrapper has a fixed signature and so
|
|
508
|
+
forwards `b` even when the caller passed none, so treating undef as a *bad* argument warned on
|
|
509
|
+
every `determinant()`-style call. Passing undef is idiomatically the same as not passing in
|
|
510
|
+
OpenSCAD, which is how BOSL2 threads optional arguments through wrappers throughout.
|
|
511
|
+
Verified against OpenSCAD 2026.02.01: the wrapper pattern resolves identically there.
|
|
512
|
+
**2D is marching squares here** — `CrossSection` has no contour extraction, only construction
|
|
513
|
+
from polygons. Three details carry the correctness: saddles (cases 5/10) resolved by the
|
|
514
|
+
cell-centre average, or the topology is a coin flip; vertices keyed on **edge identity** so
|
|
515
|
+
loops close exactly rather than nearly; and the field padded with an outside ring so no contour
|
|
516
|
+
runs off the box.
|
|
517
|
+
**The padding then has to be clipped back off.** Measured: a half-plane came out **0.9 × spacing
|
|
518
|
+
too large along every side it touched** — an O(h) error where a closed contour is O(h²) — because
|
|
519
|
+
the contour landed out in the pad. `clipToBounds` intersects with the `bounds` rectangle and the
|
|
520
|
+
answer becomes exact and resolution-independent (`LevelSet2d.AContourRunningOffTheBoxIsClippedExactly`).
|
|
521
|
+
Do not remove it, and do not "fix" it by moving the pad closer instead.
|
|
495
522
|
Sign convention: Manifold takes positive as inside; a distance field is the other way round, so
|
|
496
523
|
the default flips it and `invert=true` flips back.
|
|
497
524
|
Two behaviours that look alike and are opposite: an isovalue **nothing reaches** gives empty
|
{openscad_cpp_evaluator-0.50.0 → openscad_cpp_evaluator-0.51.0}/src/builtins/function_builtins.cpp
RENAMED
|
@@ -1345,9 +1345,19 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
|
|
|
1345
1345
|
// The OpenSCAD release we track. version_num() is that same
|
|
1346
1346
|
// year/month/day folded as y * 10000 + m * 100 + d, exactly like the
|
|
1347
1347
|
// reference's own builtin_version_num (builtin_functions.cc).
|
|
1348
|
-
case BuiltinFnId::LinearSolve:
|
|
1349
|
-
|
|
1350
|
-
|
|
1348
|
+
case BuiltinFnId::LinearSolve: {
|
|
1349
|
+
// An explicitly-undef b counts as ABSENT, not as a bad argument.
|
|
1350
|
+
// A wrapper with a fixed signature -- BOSL2's builtins.scad
|
|
1351
|
+
// pattern, `function _linear_solve(A, b) = linear_solve(A, b);`
|
|
1352
|
+
// -- always forwards b, so determinant()-style callers that pass
|
|
1353
|
+
// no right-hand side would otherwise warn on every call.
|
|
1354
|
+
// Passing undef is idiomatically the same as not passing in
|
|
1355
|
+
// OpenSCAD, which is how optional arguments are threaded through
|
|
1356
|
+
// wrappers throughout BOSL2.
|
|
1357
|
+
const Value bArg = getArg(args, 1, "b", Value{});
|
|
1358
|
+
const bool haveB = !std::holds_alternative<std::monostate>(bArg);
|
|
1359
|
+
return builtinLinearSolve(ev, getArg(args, 0, "A", Value{}), bArg, haveB, node);
|
|
1360
|
+
}
|
|
1351
1361
|
case BuiltinFnId::SupportedFeature: {
|
|
1352
1362
|
const Value name = getArg(args, 0, "feature", Value{});
|
|
1353
1363
|
const std::string* s = std::get_if<std::string>(&name);
|
|
@@ -512,6 +512,63 @@ std::optional<ScalarField> readField(const Value& val) {
|
|
|
512
512
|
return f;
|
|
513
513
|
}
|
|
514
514
|
|
|
515
|
+
// field[i][j] -> rows of equal length. The 2D twin of readField.
|
|
516
|
+
std::optional<std::vector<std::vector<double>>> readPlane(const Value& val) {
|
|
517
|
+
const ListPtr* xs = std::get_if<ListPtr>(&val);
|
|
518
|
+
if (!xs || !*xs || (*xs)->items.empty()) return std::nullopt;
|
|
519
|
+
std::vector<std::vector<double>> out;
|
|
520
|
+
out.reserve((*xs)->items.size());
|
|
521
|
+
size_t ny = 0;
|
|
522
|
+
for (size_t i = 0; i < (*xs)->items.size(); ++i) {
|
|
523
|
+
const ListPtr* ys = std::get_if<ListPtr>(&(*xs)->items[i]);
|
|
524
|
+
if (!ys || !*ys || (*ys)->items.empty()) return std::nullopt;
|
|
525
|
+
if (i == 0) ny = (*ys)->items.size();
|
|
526
|
+
else if ((*ys)->items.size() != ny) return std::nullopt;
|
|
527
|
+
std::vector<double> row;
|
|
528
|
+
row.reserve(ny);
|
|
529
|
+
for (const Value& item : (*ys)->items) {
|
|
530
|
+
const double* d = std::get_if<double>(&item);
|
|
531
|
+
if (!d || !std::isfinite(*d)) return std::nullopt;
|
|
532
|
+
row.push_back(*d);
|
|
533
|
+
}
|
|
534
|
+
out.push_back(std::move(row));
|
|
535
|
+
}
|
|
536
|
+
return out;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Like a numeric list, but INF and -INF are legal -- BOSL2 writes its
|
|
540
|
+
// open-ended ranges as [isovalue, INF].
|
|
541
|
+
std::optional<std::vector<double>> numbersOrInf(const Value& v) {
|
|
542
|
+
const ListPtr* l = std::get_if<ListPtr>(&v);
|
|
543
|
+
if (!l || !*l) return std::nullopt;
|
|
544
|
+
std::vector<double> out;
|
|
545
|
+
out.reserve((*l)->items.size());
|
|
546
|
+
for (const Value& item : (*l)->items) {
|
|
547
|
+
const double* d = std::get_if<double>(&item);
|
|
548
|
+
if (!d || std::isnan(*d)) return std::nullopt;
|
|
549
|
+
out.push_back(*d);
|
|
550
|
+
}
|
|
551
|
+
return out;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Positive inside the band [lo, hi], zero on either surface. Manifold takes
|
|
555
|
+
// POSITIVE as inside and imposes no continuity or true-distance requirement,
|
|
556
|
+
// so the min() kink at the middle of a bounded band is fine.
|
|
557
|
+
//
|
|
558
|
+
// One pass, not two: a bounded range could be had as
|
|
559
|
+
// difference(levelset(hi), levelset(lo)), and that works, but it meshes
|
|
560
|
+
// twice and then does a boolean.
|
|
561
|
+
double bandDistance(double v, double lo, double hi, bool invert) {
|
|
562
|
+
const bool loFinite = std::isfinite(lo);
|
|
563
|
+
const bool hiFinite = std::isfinite(hi);
|
|
564
|
+
double d;
|
|
565
|
+
if (loFinite && hiFinite) d = std::min(v - lo, hi - v);
|
|
566
|
+
else if (hiFinite) d = hi - v; // [-INF, hi] -- a scalar isovalue
|
|
567
|
+
else if (loFinite) d = v - lo; // [lo, INF] -- BOSL2's idiom
|
|
568
|
+
else d = 1.0; // unbounded both ways: everything is inside
|
|
569
|
+
return invert ? -d : d;
|
|
570
|
+
}
|
|
571
|
+
|
|
515
572
|
std::optional<std::array<double, 3>> readVec3(const Value& v) {
|
|
516
573
|
const ListPtr* l = std::get_if<ListPtr>(&v);
|
|
517
574
|
if (!l || !*l || (*l)->items.size() != 3) return std::nullopt;
|
|
@@ -526,6 +583,130 @@ std::optional<std::array<double, 3>> readVec3(const Value& v) {
|
|
|
526
583
|
|
|
527
584
|
} // namespace
|
|
528
585
|
|
|
586
|
+
// -- 2D: marching squares --------------------------------------------------
|
|
587
|
+
//
|
|
588
|
+
// CrossSection has no contour extraction -- it only builds from explicit
|
|
589
|
+
// polygons -- so the contours are produced here and handed to
|
|
590
|
+
// CrossSection(Polygons, FillRule).
|
|
591
|
+
//
|
|
592
|
+
// Three details are where the bugs live in this algorithm, so all three are
|
|
593
|
+
// handled explicitly rather than hoped for:
|
|
594
|
+
//
|
|
595
|
+
// * SADDLES. Cases 5 and 10 have two opposite corners inside and two out,
|
|
596
|
+
// and admit two different connections. Picking wrong changes the
|
|
597
|
+
// TOPOLOGY -- two touching blobs versus one pinched shape. Resolved with
|
|
598
|
+
// the cell-centre average (the asymptotic decider).
|
|
599
|
+
// * SHARED VERTICES. Segments are keyed on EDGE IDENTITY, never on float
|
|
600
|
+
// coordinates. Two cells sharing an edge then produce the same vertex id
|
|
601
|
+
// by construction, so loops close exactly instead of nearly.
|
|
602
|
+
// * THE BOUNDARY. A contour running off the box would be an open path. The
|
|
603
|
+
// field is padded with a ring of "outside" first, so every contour closes.
|
|
604
|
+
// That is BOSL2's closed=true, and the only behaviour offered here.
|
|
605
|
+
|
|
606
|
+
struct Contours2d {
|
|
607
|
+
std::vector<double> v; // (ny+2) x (nx+2), padded
|
|
608
|
+
size_t nx = 0, ny = 0; // padded dimensions
|
|
609
|
+
double ox = 0, oy = 0, dx = 1, dy = 1; // origin/spacing of the PADDED grid
|
|
610
|
+
double at(size_t i, size_t j) const { return v[j * nx + i]; }
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// The padded ring sits a full spacing outside the box, so a contour that
|
|
614
|
+
// runs off the edge lands out in the padding -- measured, that inflated a
|
|
615
|
+
// half-plane by 0.9 * spacing along every side it touched, an O(h) error
|
|
616
|
+
// where a closed contour is O(h^2). Clipping to the box afterwards removes
|
|
617
|
+
// exactly that overhang, and is what the caller means by `bounds` anyway.
|
|
618
|
+
manifold::CrossSection clipToBounds(const manifold::CrossSection& cs, const std::vector<double>& lo,
|
|
619
|
+
const std::vector<double>& hi) {
|
|
620
|
+
manifold::Rect box(manifold::vec2(lo[0], lo[1]), manifold::vec2(hi[0], hi[1]));
|
|
621
|
+
return cs ^ manifold::CrossSection(box);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
manifold::Polygons marchingSquares(const Contours2d& g) {
|
|
625
|
+
const size_t nx = g.nx, ny = g.ny;
|
|
626
|
+
// A vertex can sit on a horizontal edge (between i,j and i+1,j) or a
|
|
627
|
+
// vertical one (between i,j and i,j+1). Ids are derived from the edge,
|
|
628
|
+
// so the two cells sharing it agree exactly.
|
|
629
|
+
const auto hId = [&](size_t i, size_t j) { return j * (nx - 1) + i; };
|
|
630
|
+
const size_t hCount = (nx - 1) * ny;
|
|
631
|
+
const auto vId = [&](size_t i, size_t j) { return hCount + j * nx + i; };
|
|
632
|
+
|
|
633
|
+
std::unordered_map<size_t, manifold::vec2> pts;
|
|
634
|
+
const auto lerpEdge = [&](size_t i0, size_t j0, size_t i1, size_t j1, size_t id) {
|
|
635
|
+
if (pts.count(id)) return;
|
|
636
|
+
const double a = g.at(i0, j0), b = g.at(i1, j1);
|
|
637
|
+
const double t = (a == b) ? 0.5 : a / (a - b);
|
|
638
|
+
pts[id] = manifold::vec2(g.ox + (static_cast<double>(i0) + t * (static_cast<double>(i1) - static_cast<double>(i0))) * g.dx,
|
|
639
|
+
g.oy + (static_cast<double>(j0) + t * (static_cast<double>(j1) - static_cast<double>(j0))) * g.dy);
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
std::vector<std::pair<size_t, size_t>> segs;
|
|
643
|
+
for (size_t j = 0; j + 1 < ny; ++j) {
|
|
644
|
+
for (size_t i = 0; i + 1 < nx; ++i) {
|
|
645
|
+
const double d0 = g.at(i, j), d1 = g.at(i + 1, j), d2 = g.at(i + 1, j + 1), d3 = g.at(i, j + 1);
|
|
646
|
+
const int mask = (d0 > 0 ? 1 : 0) | (d1 > 0 ? 2 : 0) | (d2 > 0 ? 4 : 0) | (d3 > 0 ? 8 : 0);
|
|
647
|
+
if (mask == 0 || mask == 15) continue;
|
|
648
|
+
|
|
649
|
+
const size_t eB = hId(i, j), eT = hId(i, j + 1), eL = vId(i, j), eR = vId(i + 1, j);
|
|
650
|
+
const auto mkB = [&] { lerpEdge(i, j, i + 1, j, eB); };
|
|
651
|
+
const auto mkT = [&] { lerpEdge(i, j + 1, i + 1, j + 1, eT); };
|
|
652
|
+
const auto mkL = [&] { lerpEdge(i, j, i, j + 1, eL); };
|
|
653
|
+
const auto mkR = [&] { lerpEdge(i + 1, j, i + 1, j + 1, eR); };
|
|
654
|
+
const auto add = [&](size_t a, size_t b) { segs.emplace_back(a, b); };
|
|
655
|
+
|
|
656
|
+
switch (mask) {
|
|
657
|
+
case 1: case 14: mkL(); mkB(); add(eL, eB); break;
|
|
658
|
+
case 2: case 13: mkB(); mkR(); add(eB, eR); break;
|
|
659
|
+
case 3: case 12: mkL(); mkR(); add(eL, eR); break;
|
|
660
|
+
case 4: case 11: mkR(); mkT(); add(eR, eT); break;
|
|
661
|
+
case 6: case 9: mkB(); mkT(); add(eB, eT); break;
|
|
662
|
+
case 8: case 7: mkT(); mkL(); add(eT, eL); break;
|
|
663
|
+
case 5: case 10: {
|
|
664
|
+
// Saddle: the centre decides which way the two contours
|
|
665
|
+
// pass. Without this the topology is a coin flip.
|
|
666
|
+
mkL(); mkB(); mkR(); mkT();
|
|
667
|
+
const double centre = 0.25 * (d0 + d1 + d2 + d3);
|
|
668
|
+
const bool joinLB = (mask == 5) == (centre > 0);
|
|
669
|
+
if (joinLB) { add(eL, eB); add(eR, eT); }
|
|
670
|
+
else { add(eL, eT); add(eB, eR); }
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
default: break;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (segs.empty()) return {};
|
|
678
|
+
|
|
679
|
+
// Chain segments into closed loops. Every vertex sits on exactly two
|
|
680
|
+
// segments in a well-formed field, so this is a plain walk.
|
|
681
|
+
std::unordered_map<size_t, std::vector<size_t>> adj;
|
|
682
|
+
for (const auto& [a, b] : segs) {
|
|
683
|
+
adj[a].push_back(b);
|
|
684
|
+
adj[b].push_back(a);
|
|
685
|
+
}
|
|
686
|
+
std::unordered_set<size_t> used;
|
|
687
|
+
manifold::Polygons out;
|
|
688
|
+
for (const auto& [start, _] : adj) {
|
|
689
|
+
if (used.count(start)) continue;
|
|
690
|
+
manifold::SimplePolygon loop;
|
|
691
|
+
size_t cur = start, prev = SIZE_MAX;
|
|
692
|
+
while (true) {
|
|
693
|
+
if (used.count(cur)) break;
|
|
694
|
+
used.insert(cur);
|
|
695
|
+
loop.push_back(pts[cur]);
|
|
696
|
+
size_t next = SIZE_MAX;
|
|
697
|
+
for (size_t cand : adj[cur]) {
|
|
698
|
+
if (cand != prev && !used.count(cand)) { next = cand; break; }
|
|
699
|
+
}
|
|
700
|
+
if (next == SIZE_MAX) break;
|
|
701
|
+
prev = cur;
|
|
702
|
+
cur = next;
|
|
703
|
+
}
|
|
704
|
+
if (loop.size() >= 3) out.push_back(std::move(loop));
|
|
705
|
+
}
|
|
706
|
+
return out;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
|
|
529
710
|
CSGParams resolveLevelSet(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
|
|
530
711
|
auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
|
|
531
712
|
CSGParams params;
|
|
@@ -553,66 +734,85 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
|
|
|
553
734
|
// back from threads it knows nothing about.
|
|
554
735
|
const Value& fieldArg = params.at("field");
|
|
555
736
|
const ClosurePtr* fieldFn = std::get_if<ClosurePtr>(&fieldArg);
|
|
737
|
+
// The array is NOT parsed yet: whether it should be field[i][j] or
|
|
738
|
+
// field[i][j][k] depends on `bounds`, which is read below. Parsing it as
|
|
739
|
+
// 3D here rejected every 2D grid before the 2D path ever ran.
|
|
556
740
|
std::optional<ScalarField> field;
|
|
557
|
-
if (
|
|
558
|
-
field
|
|
559
|
-
if (!field) {
|
|
560
|
-
ev.warn("levelset(): field must be a function(x,y,z) or a rectangular field[i][j][k] of numbers",
|
|
561
|
-
&node.position());
|
|
562
|
-
return {};
|
|
563
|
-
}
|
|
564
|
-
if (field->nx < 2 || field->ny < 2 || field->nz < 2) {
|
|
565
|
-
ev.warn("levelset(): the field needs at least 2 samples along each axis", &node.position());
|
|
566
|
-
return {};
|
|
567
|
-
}
|
|
568
|
-
} else if (!*fieldFn || (*fieldFn)->node == nullptr || (*fieldFn)->node->parameters.size() < 3) {
|
|
569
|
-
ev.warn("levelset(): the field function needs three parameters, as in function(x,y,z) ...",
|
|
741
|
+
if (fieldFn && (!*fieldFn || (*fieldFn)->node == nullptr || (*fieldFn)->node->parameters.size() < 2)) {
|
|
742
|
+
ev.warn("levelset(): the field function needs function(x,y) for 2D or function(x,y,z) for 3D",
|
|
570
743
|
&node.position());
|
|
571
744
|
return {};
|
|
572
745
|
}
|
|
573
746
|
|
|
747
|
+
// 2D or 3D is decided by `bounds`, not guessed from the field: a 2-vector
|
|
748
|
+
// corner means a section, a 3-vector means a solid. Explicit, and it
|
|
749
|
+
// matches how the caller already has to think about the box.
|
|
574
750
|
const ListPtr* bb = std::get_if<ListPtr>(¶ms.at("bounds"));
|
|
575
|
-
std::optional<std::
|
|
751
|
+
std::optional<std::vector<double>> bLo, bHi;
|
|
576
752
|
if (bb && *bb && (*bb)->items.size() == 2) {
|
|
577
|
-
|
|
578
|
-
|
|
753
|
+
bLo = numbersOrInf((*bb)->items[0]);
|
|
754
|
+
bHi = numbersOrInf((*bb)->items[1]);
|
|
579
755
|
}
|
|
580
|
-
if (!
|
|
581
|
-
ev.warn("levelset(): bounds must be [[x0,y0,z0],[x1,y1,z1]]",
|
|
756
|
+
if (!bLo || !bHi || bLo->size() != bHi->size() || (bLo->size() != 2 && bLo->size() != 3)) {
|
|
757
|
+
ev.warn("levelset(): bounds must be [[x0,y0],[x1,y1]] or [[x0,y0,z0],[x1,y1,z1]]",
|
|
758
|
+
&node.position());
|
|
582
759
|
return {};
|
|
583
760
|
}
|
|
584
|
-
|
|
585
|
-
|
|
761
|
+
const bool is2d = bLo->size() == 2;
|
|
762
|
+
std::optional<std::array<double, 3>> lo, hi;
|
|
763
|
+
if (!is2d) {
|
|
764
|
+
lo = std::array<double, 3>{(*bLo)[0], (*bLo)[1], (*bLo)[2]};
|
|
765
|
+
hi = std::array<double, 3>{(*bHi)[0], (*bHi)[1], (*bHi)[2]};
|
|
766
|
+
}
|
|
767
|
+
for (size_t a = 0; a < bLo->size(); ++a) {
|
|
768
|
+
if (!((*bHi)[a] > (*bLo)[a])) {
|
|
586
769
|
ev.warn("levelset(): bounds must be increasing along every axis", &node.position());
|
|
587
770
|
return {};
|
|
588
771
|
}
|
|
589
772
|
}
|
|
590
773
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
774
|
+
// isovalue is a single level OR a bounded range [lo, hi].
|
|
775
|
+
//
|
|
776
|
+
// Unified as a band: a scalar v is [-INF, v], so "at or below" -- the
|
|
777
|
+
// distance-field reading, where smaller means further inside. A range
|
|
778
|
+
// means BETWEEN, which is what BOSL2's isosurface/contour pass around,
|
|
779
|
+
// and [lo, INF] is its "at or above" idiom. A caller handing us BOSL2's
|
|
780
|
+
// own isovalue argument therefore gets BOSL2's own semantics with no
|
|
781
|
+
// translation and no invert.
|
|
782
|
+
//
|
|
783
|
+
// Same rule as linear_solve's b: an explicitly-undef argument is ABSENT,
|
|
784
|
+
// so a fixed-signature wrapper forwarding every parameter still works.
|
|
785
|
+
const Value& isoVal = params.at("isovalue");
|
|
786
|
+
double isoLo = -std::numeric_limits<double>::infinity();
|
|
787
|
+
double isoHi = 0.0;
|
|
788
|
+
if (!std::holds_alternative<std::monostate>(isoVal)) {
|
|
789
|
+
if (const double* isoArg = std::get_if<double>(&isoVal)) {
|
|
790
|
+
isoHi = *isoArg;
|
|
791
|
+
} else if (const std::optional<std::vector<double>> pair = numbersOrInf(isoVal);
|
|
792
|
+
pair && pair->size() == 2) {
|
|
793
|
+
isoLo = (*pair)[0];
|
|
794
|
+
isoHi = (*pair)[1];
|
|
795
|
+
if (!(isoHi > isoLo)) {
|
|
796
|
+
ev.warn("levelset(): isovalue range must be increasing", &node.position());
|
|
797
|
+
return {};
|
|
798
|
+
}
|
|
799
|
+
} else {
|
|
800
|
+
ev.warn("levelset(): isovalue must be a number or a [low, high] range", &node.position());
|
|
801
|
+
return {};
|
|
802
|
+
}
|
|
595
803
|
}
|
|
596
|
-
const double isovalue = *isoArg;
|
|
597
804
|
const bool invert = truthy(params.at("invert"));
|
|
598
805
|
|
|
599
806
|
const std::array<double, 3> origin = *lo;
|
|
600
807
|
std::array<double, 3> spacing{1.0, 1.0, 1.0};
|
|
601
808
|
double edge = 0.0;
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
spacing = {((*hi)[0] - (*lo)[0]) / static_cast<double>(field->nx - 1),
|
|
605
|
-
((*hi)[1] - (*lo)[1]) / static_cast<double>(field->ny - 1),
|
|
606
|
-
((*hi)[2] - (*lo)[2]) / static_cast<double>(field->nz - 1)};
|
|
607
|
-
// Finer than the grid buys nothing -- there is no information between
|
|
608
|
-
// samples -- and coarser throws away what the script paid to compute.
|
|
609
|
-
edge = std::min({spacing[0], spacing[1], spacing[2]});
|
|
610
|
-
}
|
|
809
|
+
// Grid spacing is filled in below, once the array has been read at the
|
|
810
|
+
// right dimensionality. Finer than the grid buys nothing.
|
|
611
811
|
if (const double* e = std::get_if<double>(¶ms.at("edge"))) {
|
|
612
812
|
if (*e > 0.0) edge = *e;
|
|
613
813
|
else ev.warn("levelset(): edge must be positive", &node.position());
|
|
614
814
|
}
|
|
615
|
-
if (edge <= 0.0) {
|
|
815
|
+
if (edge <= 0.0 && fieldFn) {
|
|
616
816
|
// No grid to infer it from. Guessing would silently pick either a
|
|
617
817
|
// useless mesh or a ten-minute one -- the cost is cubic in this
|
|
618
818
|
// number, so it is the caller's call to make.
|
|
@@ -621,27 +821,133 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
|
|
|
621
821
|
}
|
|
622
822
|
|
|
623
823
|
// The function form: one closure call per sample. No live EvalContext at
|
|
624
|
-
// generate time, so a root is built from the closure's own scope
|
|
625
|
-
//
|
|
626
|
-
// their defaults inside a field function.
|
|
824
|
+
// generate time, so a root is built from the closure's own scope. Its
|
|
825
|
+
// $-variables are therefore at their defaults.
|
|
627
826
|
std::optional<EvalContext> fnCtx;
|
|
628
|
-
std::
|
|
827
|
+
std::vector<std::string> fnParams2;
|
|
629
828
|
if (fieldFn) {
|
|
630
829
|
fnCtx = EvalContext::makeRoot((*fieldFn)->node->scope());
|
|
631
|
-
for (
|
|
830
|
+
for (const auto& prm : (*fieldFn)->node->parameters) fnParams2.push_back(prm->name->name);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// ---- 2D: sample onto a padded grid, contour it, hand to CrossSection.
|
|
834
|
+
if (is2d) {
|
|
835
|
+
size_t nx = 0, ny = 0;
|
|
836
|
+
double sx = 0, sy = 0;
|
|
837
|
+
if (field) {
|
|
838
|
+
ev.warn("levelset(): a 2D field must be field[i][j]; a 3D array was given",
|
|
839
|
+
&node.position());
|
|
840
|
+
return {};
|
|
841
|
+
}
|
|
842
|
+
if (!fieldFn) {
|
|
843
|
+
const std::optional<std::vector<std::vector<double>>> plane = readPlane(fieldArg);
|
|
844
|
+
if (!plane || plane->size() < 2 || (*plane)[0].size() < 2) {
|
|
845
|
+
ev.warn("levelset(): 2D field must be a rectangular field[i][j] of numbers",
|
|
846
|
+
&node.position());
|
|
847
|
+
return {};
|
|
848
|
+
}
|
|
849
|
+
nx = plane->size();
|
|
850
|
+
ny = (*plane)[0].size();
|
|
851
|
+
sx = ((*bHi)[0] - (*bLo)[0]) / static_cast<double>(nx - 1);
|
|
852
|
+
sy = ((*bHi)[1] - (*bLo)[1]) / static_cast<double>(ny - 1);
|
|
853
|
+
Contours2d g;
|
|
854
|
+
g.nx = nx + 2;
|
|
855
|
+
g.ny = ny + 2;
|
|
856
|
+
g.dx = sx;
|
|
857
|
+
g.dy = sy;
|
|
858
|
+
g.ox = (*bLo)[0] - sx;
|
|
859
|
+
g.oy = (*bLo)[1] - sy;
|
|
860
|
+
// Padded with a ring that is firmly OUTSIDE, so a contour meeting
|
|
861
|
+
// the box edge closes along it instead of running off. This is
|
|
862
|
+
// BOSL2's closed=true, and the only behaviour offered.
|
|
863
|
+
g.v.assign(g.nx * g.ny, -1.0);
|
|
864
|
+
for (size_t i = 0; i < nx; ++i)
|
|
865
|
+
for (size_t j = 0; j < ny; ++j)
|
|
866
|
+
g.v[(j + 1) * g.nx + (i + 1)] = bandDistance((*plane)[i][j], isoLo, isoHi, invert);
|
|
867
|
+
const manifold::Polygons polys = marchingSquares(g);
|
|
868
|
+
if (polys.empty()) return {};
|
|
869
|
+
ColoredBody b;
|
|
870
|
+
b.section = clipToBounds(manifold::CrossSection(polys, manifold::CrossSection::FillRule::EvenOdd),
|
|
871
|
+
*bLo, *bHi);
|
|
872
|
+
if (b.section->IsEmpty()) return {};
|
|
873
|
+
std::vector<ColoredBody> out;
|
|
874
|
+
out.push_back(std::move(b));
|
|
875
|
+
return out;
|
|
876
|
+
}
|
|
877
|
+
// function(x,y): edge= gives the spacing, as in 3D
|
|
878
|
+
nx = static_cast<size_t>(std::floor(((*bHi)[0] - (*bLo)[0]) / edge)) + 1;
|
|
879
|
+
ny = static_cast<size_t>(std::floor(((*bHi)[1] - (*bLo)[1]) / edge)) + 1;
|
|
880
|
+
if (nx < 2 || ny < 2) {
|
|
881
|
+
ev.warn("levelset(): edge is larger than the bounds", &node.position());
|
|
882
|
+
return {};
|
|
883
|
+
}
|
|
884
|
+
sx = ((*bHi)[0] - (*bLo)[0]) / static_cast<double>(nx - 1);
|
|
885
|
+
sy = ((*bHi)[1] - (*bLo)[1]) / static_cast<double>(ny - 1);
|
|
886
|
+
Contours2d g;
|
|
887
|
+
g.nx = nx + 2;
|
|
888
|
+
g.ny = ny + 2;
|
|
889
|
+
g.dx = sx;
|
|
890
|
+
g.dy = sy;
|
|
891
|
+
g.ox = (*bLo)[0] - sx;
|
|
892
|
+
g.oy = (*bLo)[1] - sy;
|
|
893
|
+
g.v.assign(g.nx * g.ny, -1.0);
|
|
894
|
+
for (size_t i = 0; i < nx; ++i) {
|
|
895
|
+
for (size_t j = 0; j < ny; ++j) {
|
|
896
|
+
BoundArgs bound;
|
|
897
|
+
bound.set(fnParams2[0], Value{(*bLo)[0] + static_cast<double>(i) * sx});
|
|
898
|
+
bound.set(fnParams2[1], Value{(*bLo)[1] + static_cast<double>(j) * sy});
|
|
899
|
+
const Value outv =
|
|
900
|
+
ev.evalFunctionLiteralFromBound(**fieldFn, std::move(bound), *fnCtx, &node.position());
|
|
901
|
+
const double* d = std::get_if<double>(&outv);
|
|
902
|
+
const double val = (d && std::isfinite(*d)) ? *d : std::numeric_limits<double>::max();
|
|
903
|
+
g.v[(j + 1) * g.nx + (i + 1)] = bandDistance(val, isoLo, isoHi, invert);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const manifold::Polygons polys = marchingSquares(g);
|
|
907
|
+
if (polys.empty()) return {};
|
|
908
|
+
ColoredBody b;
|
|
909
|
+
b.section = clipToBounds(manifold::CrossSection(polys, manifold::CrossSection::FillRule::EvenOdd),
|
|
910
|
+
*bLo, *bHi);
|
|
911
|
+
if (b.section->IsEmpty()) return {};
|
|
912
|
+
std::vector<ColoredBody> out;
|
|
913
|
+
out.push_back(std::move(b));
|
|
914
|
+
return out;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
if (!fieldFn) {
|
|
919
|
+
field = readField(fieldArg);
|
|
920
|
+
if (!field) {
|
|
921
|
+
ev.warn("levelset(): field must be a function(x,y,z) or a rectangular field[i][j][k] of numbers",
|
|
922
|
+
&node.position());
|
|
923
|
+
return {};
|
|
924
|
+
}
|
|
925
|
+
if (field->nx < 2 || field->ny < 2 || field->nz < 2) {
|
|
926
|
+
ev.warn("levelset(): the field needs at least 2 samples along each axis", &node.position());
|
|
927
|
+
return {};
|
|
928
|
+
}
|
|
929
|
+
spacing = {((*bHi)[0] - (*bLo)[0]) / static_cast<double>(field->nx - 1),
|
|
930
|
+
((*bHi)[1] - (*bLo)[1]) / static_cast<double>(field->ny - 1),
|
|
931
|
+
((*bHi)[2] - (*bLo)[2]) / static_cast<double>(field->nz - 1)};
|
|
932
|
+
if (!std::get_if<double>(¶ms.at("edge"))) edge = std::min({spacing[0], spacing[1], spacing[2]});
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if (fieldFn && fnParams2.size() < 3) {
|
|
936
|
+
ev.warn("levelset(): a 3D field function needs three parameters, as in function(x,y,z) ...",
|
|
937
|
+
&node.position());
|
|
938
|
+
return {};
|
|
632
939
|
}
|
|
633
940
|
|
|
634
941
|
const auto sampleFn = [&](manifold::vec3 p) -> double {
|
|
635
942
|
BoundArgs bound;
|
|
636
943
|
const double xyz[3] = {p.x, p.y, p.z};
|
|
637
|
-
for (int a = 0; a < 3; ++a) bound.set(
|
|
944
|
+
for (int a = 0; a < 3; ++a) bound.set(fnParams2[static_cast<size_t>(a)], Value{xyz[a]});
|
|
638
945
|
const Value out = ev.evalFunctionLiteralFromBound(**fieldFn, std::move(bound), *fnCtx, &node.position());
|
|
639
946
|
const double* d = std::get_if<double>(&out);
|
|
640
947
|
// A field that returns junk at one point must not abort the whole
|
|
641
948
|
// build; treat it as far outside instead.
|
|
642
949
|
const double v = (d && std::isfinite(*d)) ? *d : std::numeric_limits<double>::max();
|
|
643
|
-
|
|
644
|
-
return invert ? diff : -diff;
|
|
950
|
+
return bandDistance(v, isoLo, isoHi, invert);
|
|
645
951
|
};
|
|
646
952
|
|
|
647
953
|
// Never dereferenced on the function path, but it must still be a valid
|
|
@@ -649,7 +955,7 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
|
|
|
649
955
|
// where the lambda is never called.
|
|
650
956
|
const ScalarField emptyField;
|
|
651
957
|
const ScalarField& f = field ? *field : emptyField;
|
|
652
|
-
const auto sampleGrid = [&f, origin, spacing,
|
|
958
|
+
const auto sampleGrid = [&f, origin, spacing, isoLo, isoHi, invert](manifold::vec3 p) -> double {
|
|
653
959
|
double g[3];
|
|
654
960
|
size_t i0[3];
|
|
655
961
|
double t[3];
|
|
@@ -671,10 +977,7 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
|
|
|
671
977
|
const double w = (di ? t[0] : 1.0 - t[0]) * (dj ? t[1] : 1.0 - t[1]) * (dk ? t[2] : 1.0 - t[2]);
|
|
672
978
|
acc += w * f.at(i0[0] + di, i0[1] + dj, i0[2] + dk);
|
|
673
979
|
}
|
|
674
|
-
|
|
675
|
-
// way round, which is the common case, so the default flips it.
|
|
676
|
-
const double d = acc - isovalue;
|
|
677
|
-
return invert ? d : -d;
|
|
980
|
+
return bandDistance(acc, isoLo, isoHi, invert);
|
|
678
981
|
};
|
|
679
982
|
|
|
680
983
|
manifold::Box bounds(manifold::vec3(origin[0], origin[1], origin[2]),
|