openscad-cpp-evaluator 0.50.1__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.1 → openscad_cpp_evaluator-0.51.0}/CLAUDE.md +21 -2
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/topology.cpp +345 -48
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_booleans.cpp +126 -3
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/README.md +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → 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.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → 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.1 → 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.1 → 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.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_feature_detection.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → openscad_cpp_evaluator-0.51.0}/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.50.1 → 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
|
|
|
@@ -500,6 +509,16 @@ grep for `ponytail:`.
|
|
|
500
509
|
every `determinant()`-style call. Passing undef is idiomatically the same as not passing in
|
|
501
510
|
OpenSCAD, which is how BOSL2 threads optional arguments through wrappers throughout.
|
|
502
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.
|
|
503
522
|
Sign convention: Manifold takes positive as inside; a distance field is the other way round, so
|
|
504
523
|
the default flips it and `invert=true` flips back.
|
|
505
524
|
Two behaviours that look alike and are opposite: an isovalue **nothing reaches** gives empty
|
|
@@ -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,72 +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
|
|
|
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
|
+
//
|
|
591
783
|
// Same rule as linear_solve's b: an explicitly-undef argument is ABSENT,
|
|
592
784
|
// so a fixed-signature wrapper forwarding every parameter still works.
|
|
593
785
|
const Value& isoVal = params.at("isovalue");
|
|
594
|
-
double
|
|
786
|
+
double isoLo = -std::numeric_limits<double>::infinity();
|
|
787
|
+
double isoHi = 0.0;
|
|
595
788
|
if (!std::holds_alternative<std::monostate>(isoVal)) {
|
|
596
|
-
const double* isoArg = std::get_if<double>(&isoVal)
|
|
597
|
-
|
|
598
|
-
|
|
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());
|
|
599
801
|
return {};
|
|
600
802
|
}
|
|
601
|
-
isovalue = *isoArg;
|
|
602
803
|
}
|
|
603
804
|
const bool invert = truthy(params.at("invert"));
|
|
604
805
|
|
|
605
806
|
const std::array<double, 3> origin = *lo;
|
|
606
807
|
std::array<double, 3> spacing{1.0, 1.0, 1.0};
|
|
607
808
|
double edge = 0.0;
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
spacing = {((*hi)[0] - (*lo)[0]) / static_cast<double>(field->nx - 1),
|
|
611
|
-
((*hi)[1] - (*lo)[1]) / static_cast<double>(field->ny - 1),
|
|
612
|
-
((*hi)[2] - (*lo)[2]) / static_cast<double>(field->nz - 1)};
|
|
613
|
-
// Finer than the grid buys nothing -- there is no information between
|
|
614
|
-
// samples -- and coarser throws away what the script paid to compute.
|
|
615
|
-
edge = std::min({spacing[0], spacing[1], spacing[2]});
|
|
616
|
-
}
|
|
809
|
+
// Grid spacing is filled in below, once the array has been read at the
|
|
810
|
+
// right dimensionality. Finer than the grid buys nothing.
|
|
617
811
|
if (const double* e = std::get_if<double>(¶ms.at("edge"))) {
|
|
618
812
|
if (*e > 0.0) edge = *e;
|
|
619
813
|
else ev.warn("levelset(): edge must be positive", &node.position());
|
|
620
814
|
}
|
|
621
|
-
if (edge <= 0.0) {
|
|
815
|
+
if (edge <= 0.0 && fieldFn) {
|
|
622
816
|
// No grid to infer it from. Guessing would silently pick either a
|
|
623
817
|
// useless mesh or a ten-minute one -- the cost is cubic in this
|
|
624
818
|
// number, so it is the caller's call to make.
|
|
@@ -627,27 +821,133 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
|
|
|
627
821
|
}
|
|
628
822
|
|
|
629
823
|
// The function form: one closure call per sample. No live EvalContext at
|
|
630
|
-
// generate time, so a root is built from the closure's own scope
|
|
631
|
-
//
|
|
632
|
-
// 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.
|
|
633
826
|
std::optional<EvalContext> fnCtx;
|
|
634
|
-
std::
|
|
827
|
+
std::vector<std::string> fnParams2;
|
|
635
828
|
if (fieldFn) {
|
|
636
829
|
fnCtx = EvalContext::makeRoot((*fieldFn)->node->scope());
|
|
637
|
-
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 {};
|
|
638
939
|
}
|
|
639
940
|
|
|
640
941
|
const auto sampleFn = [&](manifold::vec3 p) -> double {
|
|
641
942
|
BoundArgs bound;
|
|
642
943
|
const double xyz[3] = {p.x, p.y, p.z};
|
|
643
|
-
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]});
|
|
644
945
|
const Value out = ev.evalFunctionLiteralFromBound(**fieldFn, std::move(bound), *fnCtx, &node.position());
|
|
645
946
|
const double* d = std::get_if<double>(&out);
|
|
646
947
|
// A field that returns junk at one point must not abort the whole
|
|
647
948
|
// build; treat it as far outside instead.
|
|
648
949
|
const double v = (d && std::isfinite(*d)) ? *d : std::numeric_limits<double>::max();
|
|
649
|
-
|
|
650
|
-
return invert ? diff : -diff;
|
|
950
|
+
return bandDistance(v, isoLo, isoHi, invert);
|
|
651
951
|
};
|
|
652
952
|
|
|
653
953
|
// Never dereferenced on the function path, but it must still be a valid
|
|
@@ -655,7 +955,7 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
|
|
|
655
955
|
// where the lambda is never called.
|
|
656
956
|
const ScalarField emptyField;
|
|
657
957
|
const ScalarField& f = field ? *field : emptyField;
|
|
658
|
-
const auto sampleGrid = [&f, origin, spacing,
|
|
958
|
+
const auto sampleGrid = [&f, origin, spacing, isoLo, isoHi, invert](manifold::vec3 p) -> double {
|
|
659
959
|
double g[3];
|
|
660
960
|
size_t i0[3];
|
|
661
961
|
double t[3];
|
|
@@ -677,10 +977,7 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
|
|
|
677
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]);
|
|
678
978
|
acc += w * f.at(i0[0] + di, i0[1] + dj, i0[2] + dk);
|
|
679
979
|
}
|
|
680
|
-
|
|
681
|
-
// way round, which is the common case, so the default flips it.
|
|
682
|
-
const double d = acc - isovalue;
|
|
683
|
-
return invert ? d : -d;
|
|
980
|
+
return bandDistance(acc, isoLo, isoHi, invert);
|
|
684
981
|
};
|
|
685
982
|
|
|
686
983
|
manifold::Box bounds(manifold::vec3(origin[0], origin[1], origin[2]),
|