openscad-cpp-evaluator 1.15.0__tar.gz → 1.18.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-1.15.0 → openscad_cpp_evaluator-1.18.0}/CLAUDE.md +55 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/bindings/module.cpp +12 -10
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/colored_body.hpp +43 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/eval_use.hpp +12 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/evaluator.hpp +30 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/scope_trail.hpp +40 -4
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/python/openscad_cpp_evaluator/__init__.py +5 -2
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/booleans.cpp +32 -3
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/color.cpp +2 -2
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/control.cpp +1 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/bytecode_vm.cpp +1 -1
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/csg_generate.cpp +22 -2
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/csg_resolve.cpp +2 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/eval_use.cpp +6 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/expr_eval.cpp +35 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/user_calls.cpp +6 -2
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_booleans.cpp +27 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_include_cache.cpp +2 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_manifold_cache.cpp +7 -8
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_scope_trail.cpp +64 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_use.cpp +56 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tools/cli/cli_lib.cpp +1 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/README.md +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/format_closure.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/utf8.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_feature_detection.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.18.0}/uv.lock +0 -0
|
@@ -449,6 +449,39 @@ closed in one pass after a fresh "anything still unimplemented?" audit. Each bel
|
|
|
449
449
|
all (`resolve_use_scopes`/`UseStatement` had zero hits in `openscad_evaluator`'s own `tests/`), so
|
|
450
450
|
these were verified against real reference *behavior*, not ported from an existing test.
|
|
451
451
|
|
|
452
|
+
**File-level globals are evaluated ONCE per run (v1.16.0).** The main file's and every
|
|
453
|
+
`include`'s are, by the top-level statement pass, into the root `let_` level; a `use`d file's
|
|
454
|
+
are not -- `use` injects only declarations -- so `Evaluator::fileGlobal` evaluates ALL of that
|
|
455
|
+
file's top-level assignments, in source order, into a context of its own the first time anything
|
|
456
|
+
in the file reads one, and keeps it for the run. `ResolvedUseScopes::usedFileGlobals` carries
|
|
457
|
+
the per-file assignment lists and every entry point hands them over with
|
|
458
|
+
`Evaluator::setUsedFileGlobals` (CLI, both binding paths, the tests); a caller that skips that
|
|
459
|
+
keeps the old per-read fallback. Before this, `evalIdentifier`'s scope fallback re-evaluated the
|
|
460
|
+
assignment's expression on EVERY read from inside a function -- `callCtx()` isolates `let_` so the
|
|
461
|
+
root level was invisible -- which made `r = rands(0,1,1); function g() = r;` give `g() != g()`
|
|
462
|
+
(OpenSCAD: equal) and a 20K-element global list read 2000 times from a function cost 9.7s
|
|
463
|
+
(now 0.02s). Two deliberate divergences from OpenSCAD 2026.02.01, both verified against it:
|
|
464
|
+
a used file's globals are evaluated once, not once per call into the file (upstream's per-call
|
|
465
|
+
re-evaluation is a long-standing bug there, so its forward-read warning prints once here, not
|
|
466
|
+
per call); and a global read from a function BEFORE its assignment has run is `undef` with an
|
|
467
|
+
"Ignoring unknown variable" warning, exactly as upstream, where the old fallback had quietly
|
|
468
|
+
computed the not-yet-assigned value.
|
|
469
|
+
**Scope-trail level table shrinks (v1.18.0).** `ScopeTrailStorage::parent_` and its indexed twin
|
|
470
|
+
map level -> parent level in a vector indexed by level. Levels came from `++nextLevel_` and were
|
|
471
|
+
never reused, so the vector grew 4 bytes per level for the whole run -- 3 x 64MB on a 13M-call
|
|
472
|
+
script, plus the copy at every doubling -- and was 192MB of fractal_tree.scad's peak. A popped
|
|
473
|
+
level is now marked -1 (0 still means "live, no parent": an isolated root), and when the NEWEST
|
|
474
|
+
level dies `markDead` pops the vector back past every dead level above the newest live one, whose
|
|
475
|
+
numbers `openLevel` then hands out again. Every invariant `lookup()`'s sorted-merge walk relies on
|
|
476
|
+
holds: a reused number is greater than every level still holding an entry (all higher levels were
|
|
477
|
+
dead and `popLevel` had already removed their entries), so a name's push-stack stays ascending,
|
|
478
|
+
and a live level's number never changes -- an escaping closure that keeps a level alive simply
|
|
479
|
+
stops the shrink at it (`tests/test_scope_trail.cpp` covers both). The ancestry walk now stops at
|
|
480
|
+
`ancestor > 0` rather than `!= 0`. Same PR: `callCtxFor`'s per-call scan over active declarations
|
|
481
|
+
does the integer span test before the origin STRING compare (`_platform_memcmp` was 1.4% of
|
|
482
|
+
fractal_tree), and driveVm's completion branch no longer copies the finished frame's name. Anklet
|
|
483
|
+
74 -> 63MB, a 4M-call synthetic 380 -> 258MB, fractal_tree -3.4% wall.
|
|
484
|
+
|
|
452
485
|
**Known gap closed in Phase 3, worth remembering for Phase 5+**: every builtin resolve function must
|
|
453
486
|
call `resolveCallArgs` (call_args.hpp), never bare `resolveArgs`, or a `$`-prefixed named call
|
|
454
487
|
argument (`circle(r=2, $fn=64)`) silently fails to reach `ctx.dyn`. Caught by a geometric-correctness
|
|
@@ -910,6 +943,28 @@ grep for `ponytail:`.
|
|
|
910
943
|
operation`. `linear_extrude`/`rotate_extrude`/`offset` are always-2D and `projection` always-3D, so
|
|
911
944
|
they skip the "mixing" line and emit only the second; `roof()` emits neither, matching the
|
|
912
945
|
reference. Background (`%`) children are exempt.
|
|
946
|
+
**The emptiness test is cached on the body (`ColoredBody::knownEmpty`, v1.17.0).** It used to
|
|
947
|
+
call `Manifold::NumTri()` on every child at every level, and every Manifold accessor goes through
|
|
948
|
+
`GetImpl()`, which materialises a lazy transform (a full mesh + collider copy) and runs a lazy
|
|
949
|
+
boolean -- so each leaf was copied once per ancestor transform, and because every node kept its
|
|
950
|
+
`bodies` until the tree died, all those copies stayed alive: fractal_tree.scad held 72,929
|
|
951
|
+
transformed copies of 1024 leaves (3.1GB). Now a body is asked once, the flag rides along on the
|
|
952
|
+
struct copies transform/colour/flatten make (emptiness is transform-invariant, and every generate
|
|
953
|
+
function that builds a new body starts from a fresh `ColoredBody`), and `generateTreeImpl` clears
|
|
954
|
+
each child's `bodies` once the parent has consumed them (move-assigning a fresh vector: `clear()`
|
|
955
|
+
and `= {}` both keep the capacity, and 35K nodes x 592-byte slots was 230MB) -- only top-level
|
|
956
|
+
bodies leave, a cache hit re-fills from the cache, and the bindings' `csg_tree` facade no longer
|
|
957
|
+
converts per-node bodies (it used to `GetMeshGL()` every node into numpy on every evaluate(), for
|
|
958
|
+
a dump that never read them). Three more forcing points had to go for the chain to stay lazy: the
|
|
959
|
+
booleans' `Status()` operand filter (now `bodyStatus()`, same cache), the boolean RESULT's own
|
|
960
|
+
emptiness (derived from the operands' flags -- a union is non-empty if any operand is -- rather
|
|
961
|
+
than asked), and `attachTriColors`, which `GetMeshGL()`'d every boolean result and now runs only
|
|
962
|
+
when the operands could actually disagree on colour. `Evaluator::generatedNodeCount` replaced
|
|
963
|
+
"does the child have bodies" as the cache-hit proxy in tests. Transforms compose lazily inside
|
|
964
|
+
Manifold and each leaf is transformed once, at the boolean that needs it. fractal_tree 3.1GB ->
|
|
965
|
+
0.67GB, Dalek 370 -> 277MB, snappy-reprap full_assembly 1.6GB -> 0.87GB; wall time neutral to
|
|
966
|
+
-3% interleaved. The output is geometrically identical (triangle count, area, volume) but not
|
|
967
|
+
byte-identical: Manifold evaluates one flat batch union instead of our old level-by-level order.
|
|
913
968
|
|
|
914
969
|
Doing this centrally is also what keeps `generateCsg` safe: it builds ONE result and switches on
|
|
915
970
|
whether it holds a `body` or a `section`, so a group that changed dimension part-way dereferenced
|
|
@@ -223,8 +223,9 @@ nb::dict idSpansToDict(const std::vector<IdSpan>& idSpans) {
|
|
|
223
223
|
return d;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
// CSGNode -> a facade `_CSGNode` (kind/params/
|
|
227
|
-
// recursively, eagerly converting every field
|
|
226
|
+
// CSGNode -> a facade `_CSGNode` (kind/params/is_builtin/children; `bodies`
|
|
227
|
+
// is always empty, see below), recursively, eagerly converting every field
|
|
228
|
+
// to plain Python data. No
|
|
228
229
|
// lifetime extension needed -- CSGParams (Value-only, see csg_node.hpp) and
|
|
229
230
|
// ColoredBody never hold AST pointers, so the result is fully self-contained
|
|
230
231
|
// once built, unlike CSGNode::node itself (deliberately not exposed: the AST
|
|
@@ -234,15 +235,14 @@ nb::dict idSpansToDict(const std::vector<IdSpan>& idSpans) {
|
|
|
234
235
|
nb::object csgNodeToPy(const oscadeval::CSGNode& node) {
|
|
235
236
|
nb::dict params;
|
|
236
237
|
for (const auto& [k, v] : node.params) params[nb::str(k.c_str())] = valueToPy(v);
|
|
238
|
+
// Deliberately no bodies. This used to GetMeshGL() every body of every
|
|
239
|
+
// node in the tree into numpy on every evaluate() -- and the only
|
|
240
|
+
// consumer, format_csg_tree(), never read them. Since v1.17.0 an inner
|
|
241
|
+
// node's bodies are released once its parent has consumed them (see
|
|
242
|
+
// generateTreeImpl), so there is nothing here to convert anyway; and a
|
|
243
|
+
// body the tree still holds may be a LAZY Manifold whose GetMeshGL()
|
|
244
|
+
// would materialise the very transform that release exists to avoid.
|
|
237
245
|
nb::list bodies;
|
|
238
|
-
for (const oscadeval::ColoredBody& cb : node.bodies) {
|
|
239
|
-
if (!cb.isDisplayOnly() && (!cb.body || cb.body->IsEmpty())) continue;
|
|
240
|
-
// ColoredBody here is const in this walk (tree ownership stays with
|
|
241
|
-
// the caller) -- GetMeshGL() itself is logically read-only, so a
|
|
242
|
-
// const_cast here is safe/local, mirroring bodyToDict's mutating
|
|
243
|
-
// signature only because manifold3d's own API isn't const-qualified.
|
|
244
|
-
bodies.append(bodyToDict(const_cast<oscadeval::ColoredBody&>(cb)));
|
|
245
|
-
}
|
|
246
246
|
nb::list children;
|
|
247
247
|
for (const std::unique_ptr<oscadeval::CSGNode>& child : node.children) children.append(csgNodeToPy(*child));
|
|
248
248
|
return facadeAttr("_CSGNode")(node.kind, params, bodies, node.isBuiltin, children);
|
|
@@ -457,6 +457,7 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
|
|
|
457
457
|
oscad::ParsedProgram program = oscad::getProgramFromFile(path);
|
|
458
458
|
oscadeval::ResolvedUseScopes used = oscadeval::resolveUseScopes(program.nodes, path, logFn);
|
|
459
459
|
oscadeval::Evaluator ev(logFn, nullptr, manifoldCache, oscadeval::DebugHooks{}, profile);
|
|
460
|
+
ev.setUsedFileGlobals(used.usedFileGlobals);
|
|
460
461
|
oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
|
|
461
462
|
bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp, generate));
|
|
462
463
|
collectIdSpans(ev, idSpans);
|
|
@@ -764,6 +765,7 @@ nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::c
|
|
|
764
765
|
oscadeval::Evaluator ev(echoCpp, nullptr, manifoldCache, hooks, false);
|
|
765
766
|
evPtr = &ev;
|
|
766
767
|
if (fastContinueSignal) ev.setFastContinueInterruptFlag(fastContinueSignal->flag());
|
|
768
|
+
ev.setUsedFileGlobals(used.usedFileGlobals);
|
|
767
769
|
oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
|
|
768
770
|
bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp));
|
|
769
771
|
collectIdSpans(ev, idSpans);
|
|
@@ -53,6 +53,22 @@ struct ColoredBody {
|
|
|
53
53
|
BodyRole role = BodyRole::Normal;
|
|
54
54
|
std::optional<std::vector<std::array<float, 4>>> triColors; // per-triangle RGBA, multi-color CSG merges only
|
|
55
55
|
|
|
56
|
+
// Whether `body` is empty, once anything has asked (isEmptyBody,
|
|
57
|
+
// csg_generate.cpp). Asking Manifold is not free: every accessor goes
|
|
58
|
+
// through GetImpl(), which MATERIALIZES a lazy transform (a full copy of
|
|
59
|
+
// the mesh and its collider) and, for a boolean, runs it. Emptiness is
|
|
60
|
+
// invariant under transforms and colour, and every generate function
|
|
61
|
+
// that produces a genuinely new body starts from a fresh ColoredBody,
|
|
62
|
+
// so a copy carrying this flag is always still right. Before this, the
|
|
63
|
+
// dimension check at every tree level materialised every child at
|
|
64
|
+
// every level: fractal_tree.scad held 72,929 transformed copies of its
|
|
65
|
+
// 1024 leaf meshes at once (3.1GB).
|
|
66
|
+
std::optional<bool> knownEmpty;
|
|
67
|
+
// Same idea for Manifold::Status(), which the booleans consult to keep an
|
|
68
|
+
// invalid operand from poisoning a union: validity is transform-invariant
|
|
69
|
+
// too, and a boolean of valid operands is valid.
|
|
70
|
+
std::optional<manifold::Manifold::Error> knownStatus;
|
|
71
|
+
|
|
56
72
|
// Set ONLY when a mesh could not be built into a valid Manifold -- in
|
|
57
73
|
// practice a polyhedron() whose faces don't close the surface (it has
|
|
58
74
|
// boundary edges), which Manifold reports by returning an *empty* body
|
|
@@ -75,6 +91,33 @@ struct ColoredBody {
|
|
|
75
91
|
bool isDisplayOnly() const { return rawMesh.has_value(); }
|
|
76
92
|
};
|
|
77
93
|
|
|
94
|
+
// The cached questions -- see ColoredBody::knownEmpty. Ask Manifold once per
|
|
95
|
+
// body, never once per tree level.
|
|
96
|
+
inline bool bodyIsEmpty(ColoredBody& b) {
|
|
97
|
+
if (!b.knownEmpty) b.knownEmpty = !b.body || b.body->IsEmpty();
|
|
98
|
+
return *b.knownEmpty;
|
|
99
|
+
}
|
|
100
|
+
inline manifold::Manifold::Error bodyStatus(ColoredBody& b) {
|
|
101
|
+
if (!b.knownStatus) b.knownStatus = b.body ? b.body->Status() : manifold::Manifold::Error::NoError;
|
|
102
|
+
return *b.knownStatus;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Emptiness of a union, from its operands' own flags and nothing else:
|
|
106
|
+
// non-empty if any operand is known non-empty, empty if every one is known
|
|
107
|
+
// empty, otherwise unknown (asked of Manifold later, if anyone ever asks).
|
|
108
|
+
// `get` maps a range element to its ColoredBody.
|
|
109
|
+
template <typename Range, typename Get>
|
|
110
|
+
std::optional<bool> unionEmptyOf(const Range& ops, Get get) {
|
|
111
|
+
bool allKnownEmpty = true;
|
|
112
|
+
for (const auto& o : ops) {
|
|
113
|
+
const ColoredBody& b = get(o);
|
|
114
|
+
if (b.knownEmpty && !*b.knownEmpty) return false;
|
|
115
|
+
if (!b.knownEmpty) allKnownEmpty = false;
|
|
116
|
+
}
|
|
117
|
+
if (allKnownEmpty) return true;
|
|
118
|
+
return std::nullopt;
|
|
119
|
+
}
|
|
120
|
+
|
|
78
121
|
// `EvalContext::color`/CSGParams round-trip: a resolve function reads
|
|
79
122
|
// ctx.color (double-precision, matching Value's own numeric type) but a
|
|
80
123
|
// generate function only sees CSGParams (Value-typed, no ctx access) -- so
|
|
@@ -15,9 +15,21 @@ namespace oscadeval {
|
|
|
15
15
|
// must keep this struct alive for as long as evaluation runs against
|
|
16
16
|
// .rootScope/.processedNodes, since declarations injected from a used file
|
|
17
17
|
// are raw (non-owning) pointers into it.
|
|
18
|
+
// One used file's own top-level assignments, in source order, keyed by
|
|
19
|
+
// that file's root Scope (the one its injected declarations were
|
|
20
|
+
// re-anchored onto). Evaluator::setUsedFileGlobals takes the list so a
|
|
21
|
+
// used file's globals can be evaluated ONCE per run, eagerly, the first
|
|
22
|
+
// time anything in that file reads one -- see Evaluator::fileGlobal.
|
|
23
|
+
// Raw pointers into usedFileAsts, same lifetime contract as processedNodes.
|
|
24
|
+
struct UsedFileGlobals {
|
|
25
|
+
const oscad::Scope* root = nullptr;
|
|
26
|
+
std::vector<const oscad::ASTNode*> assignments;
|
|
27
|
+
};
|
|
28
|
+
|
|
18
29
|
struct ResolvedUseScopes {
|
|
19
30
|
std::vector<std::vector<std::unique_ptr<oscad::ASTNode>>> usedFileAsts;
|
|
20
31
|
std::vector<std::unique_ptr<oscad::Scope>> usedFileScopes;
|
|
32
|
+
std::vector<UsedFileGlobals> usedFileGlobals;
|
|
21
33
|
// What currentFile should actually be evaluated as: declarations
|
|
22
34
|
// injected via `use` (raw pointers into usedFileAsts, above), followed
|
|
23
35
|
// by currentFile's own nodes (raw pointers into the caller-owned
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#include "openscad_cpp_evaluator/csg_node.hpp"
|
|
8
8
|
#include "openscad_cpp_evaluator/debug_hooks.hpp"
|
|
9
9
|
#include "openscad_cpp_evaluator/eval_context.hpp"
|
|
10
|
+
#include "openscad_cpp_evaluator/eval_use.hpp"
|
|
10
11
|
#include "openscad_cpp_evaluator/eval_error.hpp"
|
|
11
12
|
#include "openscad_cpp_evaluator/font_provider.hpp"
|
|
12
13
|
#include "openscad_cpp_evaluator/manifold_cache.hpp"
|
|
@@ -285,6 +286,11 @@ public:
|
|
|
285
286
|
const oscad::ASTNode* producer);
|
|
286
287
|
|
|
287
288
|
std::unordered_map<uint32_t, const oscad::ASTNode*> idToNode;
|
|
289
|
+
// Nodes whose bodies were actually generated this run (not served from
|
|
290
|
+
// the ManifoldCache). Reset per run; a diagnostic, and the only honest
|
|
291
|
+
// way for a test to tell a cache hit from a miss now that an inner
|
|
292
|
+
// node's bodies are released the moment its parent has consumed them.
|
|
293
|
+
std::size_t generatedNodeCount = 0;
|
|
288
294
|
std::unordered_map<uint32_t, std::optional<std::array<float, 4>>> idToColor;
|
|
289
295
|
|
|
290
296
|
// Called by primitive-construction generate functions (cube, sphere,
|
|
@@ -1396,6 +1402,11 @@ public:
|
|
|
1396
1402
|
// the same decision evalUserFunction*/evalFunctionLiteral* already
|
|
1397
1403
|
// make before calling lookupOrCompileChunk/lookupCompiledLiteralChunk
|
|
1398
1404
|
// themselves.
|
|
1405
|
+
// Every used file's own top-level assignments (ResolvedUseScopes::
|
|
1406
|
+
// usedFileGlobals), so fileGlobal() can evaluate a file's globals once.
|
|
1407
|
+
// A caller that skips this keeps the old per-read fallback for used files.
|
|
1408
|
+
void setUsedFileGlobals(std::vector<UsedFileGlobals> globals) { usedFileGlobals_ = std::move(globals); }
|
|
1409
|
+
|
|
1399
1410
|
bool useBytecodeVm() const {
|
|
1400
1411
|
return bytecodeVmEnabled() && (!debugHooks_.debugHook || fastContinueBreakpoints_.has_value());
|
|
1401
1412
|
}
|
|
@@ -1941,6 +1952,25 @@ private:
|
|
|
1941
1952
|
// lifetime contract as every other raw AST/EvalContext pointer this
|
|
1942
1953
|
// class holds).
|
|
1943
1954
|
const EvalContext* rootCtx_ = nullptr;
|
|
1955
|
+
|
|
1956
|
+
// File-level globals are evaluated ONCE per run. The main file's (and
|
|
1957
|
+
// every include's) already are, by the top-level statement pass, into
|
|
1958
|
+
// rootCtx_'s own let_ level. A used file's are not -- `use` injects only
|
|
1959
|
+
// its declarations -- so the first read of any global from that file
|
|
1960
|
+
// evaluates ALL of its assignments, in source order, into a context of
|
|
1961
|
+
// its own, kept here for the rest of the run. Before this, a function
|
|
1962
|
+
// reading a global re-evaluated the assignment's expression on every
|
|
1963
|
+
// read (evalIdentifier's scope fallback): `r = rands(0,1,1); function
|
|
1964
|
+
// g() = r;` gave `g() != g()`, and a 20K-element global list read 2000
|
|
1965
|
+
// times from a function cost 9.7s. OpenSCAD itself re-evaluates a used
|
|
1966
|
+
// file's globals per CALL, which is a long-standing upstream bug, not
|
|
1967
|
+
// behaviour to match. Returns nullopt for a file this evaluator was
|
|
1968
|
+
// never told about (setUsedFileGlobals), so such a caller keeps the
|
|
1969
|
+
// old fallback; nullptr for a known file whose global is not (yet)
|
|
1970
|
+
// assigned -- a forward read, undef in OpenSCAD too.
|
|
1971
|
+
std::optional<const Value*> fileGlobal(const oscad::Scope& fileRoot, const std::string& name);
|
|
1972
|
+
std::vector<UsedFileGlobals> usedFileGlobals_;
|
|
1973
|
+
std::unordered_map<const oscad::Scope*, EvalContext> usedFileCtx_;
|
|
1944
1974
|
// The most recent ctx seen by evalStatement() -- error()'s only way to
|
|
1945
1975
|
// reach a locals snapshot for its own errorBreak hook call, since
|
|
1946
1976
|
// error() itself (called from dozens of builtin resolve functions)
|
|
@@ -186,7 +186,7 @@ public:
|
|
|
186
186
|
}
|
|
187
187
|
dirty_.erase(it);
|
|
188
188
|
}
|
|
189
|
-
|
|
189
|
+
markDead(level);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
// nullptr if `name` has no binding on `myLevel`'s own ancestry chain.
|
|
@@ -211,7 +211,7 @@ public:
|
|
|
211
211
|
const std::vector<Entry>& vec = it->second;
|
|
212
212
|
auto idx = static_cast<long>(vec.size()) - 1;
|
|
213
213
|
int ancestor = myLevel;
|
|
214
|
-
while (ancestor
|
|
214
|
+
while (ancestor > 0 && idx >= 0) {
|
|
215
215
|
const int entryLevel = vec[static_cast<size_t>(idx)].level;
|
|
216
216
|
if (entryLevel > ancestor) {
|
|
217
217
|
--idx;
|
|
@@ -236,6 +236,11 @@ public:
|
|
|
236
236
|
auto it = stacks_.find(name);
|
|
237
237
|
return it == stacks_.end() ? 0 : it->second.size();
|
|
238
238
|
}
|
|
239
|
+
// Test-only: how many level slots `parent_` currently holds (index 0
|
|
240
|
+
// included) -- lets ScopeTrailStorageTest assert that popping levels in
|
|
241
|
+
// call/return order really does shrink it back, and that an escaping
|
|
242
|
+
// closure's still-live level stops the shrink at itself.
|
|
243
|
+
size_t debugLevelSlotsForTesting() const { return parent_.size(); }
|
|
239
244
|
|
|
240
245
|
// Ancestry-filtered snapshot -- O(total distinct names ever bound so
|
|
241
246
|
// far in the whole evaluation), not O(names visible now). Only used
|
|
@@ -272,6 +277,27 @@ private:
|
|
|
272
277
|
std::vector<int> parent_{0};
|
|
273
278
|
int nextLevel_ = 0;
|
|
274
279
|
|
|
280
|
+
// A popped level is marked -1 (0 is a live level with no parent -- an
|
|
281
|
+
// isolated root -- so the two must stay distinguishable). Then, whenever
|
|
282
|
+
// the level being popped is the newest one, `parent_` shrinks back past
|
|
283
|
+
// every dead level above the newest still-live one, and its number is
|
|
284
|
+
// handed out again by the next openLevel(). This keeps every invariant
|
|
285
|
+
// lookup() relies on: a reused number is greater than every level still
|
|
286
|
+
// holding an entry (all higher levels were dead, and popLevel already
|
|
287
|
+
// removed their entries), so a name's push-stack stays ascending, and a
|
|
288
|
+
// live level's number never changes -- an escaping closure that keeps a
|
|
289
|
+
// level alive simply stops the shrink at it. Without this, `parent_`
|
|
290
|
+
// grew by one int per level for the whole run and never shrank: 3 x
|
|
291
|
+
// 64MB for a 13M-call script, plus the copy of each doubling.
|
|
292
|
+
void markDead(int level) {
|
|
293
|
+
parent_[static_cast<size_t>(level)] = -1;
|
|
294
|
+
if (level == nextLevel_) {
|
|
295
|
+
while (nextLevel_ > 0 && parent_[static_cast<size_t>(nextLevel_)] < 0) {
|
|
296
|
+
parent_.pop_back();
|
|
297
|
+
--nextLevel_;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
275
301
|
};
|
|
276
302
|
|
|
277
303
|
// Per-EvalContext handle onto a shared ScopeTrailStorage<T>: this view's
|
|
@@ -473,7 +499,7 @@ public:
|
|
|
473
499
|
}
|
|
474
500
|
dirty_.erase(it);
|
|
475
501
|
}
|
|
476
|
-
|
|
502
|
+
markDead(level);
|
|
477
503
|
}
|
|
478
504
|
|
|
479
505
|
// Same sorted-merge ancestry walk as ScopeTrailStorage::lookup -- see
|
|
@@ -485,7 +511,7 @@ public:
|
|
|
485
511
|
const std::vector<Entry>& vec = stacks_[static_cast<size_t>(id)];
|
|
486
512
|
auto idx = static_cast<long>(vec.size()) - 1;
|
|
487
513
|
int ancestor = myLevel;
|
|
488
|
-
while (ancestor
|
|
514
|
+
while (ancestor > 0 && idx >= 0) {
|
|
489
515
|
const int entryLevel = vec[static_cast<size_t>(idx)].level;
|
|
490
516
|
if (entryLevel > ancestor) {
|
|
491
517
|
--idx;
|
|
@@ -505,6 +531,7 @@ public:
|
|
|
505
531
|
int id = intern_->idFor(name);
|
|
506
532
|
return id >= 0 && static_cast<size_t>(id) < stacks_.size() ? stacks_[static_cast<size_t>(id)].size() : 0;
|
|
507
533
|
}
|
|
534
|
+
size_t debugLevelSlotsForTesting() const { return parent_.size(); } // see ScopeTrailStorage's
|
|
508
535
|
|
|
509
536
|
std::vector<std::pair<std::string, T>> items(int myLevel) const {
|
|
510
537
|
std::vector<std::pair<std::string, T>> result;
|
|
@@ -530,6 +557,15 @@ private:
|
|
|
530
557
|
std::vector<int> parent_{0}; // see ScopeTrailStorage::parent_
|
|
531
558
|
int nextLevel_ = 0;
|
|
532
559
|
|
|
560
|
+
void markDead(int level) { // see ScopeTrailStorage::markDead
|
|
561
|
+
parent_[static_cast<size_t>(level)] = -1;
|
|
562
|
+
if (level == nextLevel_) {
|
|
563
|
+
while (nextLevel_ > 0 && parent_[static_cast<size_t>(nextLevel_)] < 0) {
|
|
564
|
+
parent_.pop_back();
|
|
565
|
+
--nextLevel_;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
533
569
|
};
|
|
534
570
|
|
|
535
571
|
// Per-EvalContext handle onto a shared IndexedScopeTrailStorage<T> -- the
|
|
@@ -123,8 +123,11 @@ class RootScope:
|
|
|
123
123
|
|
|
124
124
|
class _CSGNode:
|
|
125
125
|
"""One resolved (and, after evaluate(), generated) node in the CSG tree --
|
|
126
|
-
mirrors the reference's CSGNode dataclass. `bodies` is
|
|
127
|
-
|
|
126
|
+
mirrors the reference's CSGNode dataclass. `bodies` is always empty since
|
|
127
|
+
v1.17.0: an inner node's bodies are released as soon as its parent has
|
|
128
|
+
consumed them, and converting every node's mesh to numpy on every
|
|
129
|
+
evaluate() was work nothing read (format_csg_tree() prints structure,
|
|
130
|
+
not geometry; the rendered bodies come back separately). `params` is
|
|
128
131
|
plain Python data (numbers/strings/bools/lists/dicts/OscObject), never an
|
|
129
132
|
AST pointer -- eagerly converted, no lifetime tie to the Evaluator that
|
|
130
133
|
produced it. Used by format_csg_tree(); not otherwise part of the public
|
|
@@ -286,6 +286,14 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params, con
|
|
|
286
286
|
std::vector<Part2d> res2d;
|
|
287
287
|
bool have2d = false;
|
|
288
288
|
size_t idx = 0;
|
|
289
|
+
// Whether the 3D operands could possibly disagree on colour. Only then
|
|
290
|
+
// is attachTriColors worth its GetMeshGL(), which evaluates the boolean
|
|
291
|
+
// and copies the whole mesh out -- at EVERY level of a nested union
|
|
292
|
+
// chain, when nothing but the top ever needed it. Operands that all
|
|
293
|
+
// carry the same colour and no per-triangle colours of their own can
|
|
294
|
+
// only produce a uniformly coloured result.
|
|
295
|
+
std::optional<std::optional<std::array<float, 4>>> firstColor;
|
|
296
|
+
bool mixedColors = false;
|
|
289
297
|
|
|
290
298
|
size_t stmtIndex = 0;
|
|
291
299
|
for (const Value& sizeVal : groupSizes) {
|
|
@@ -314,7 +322,7 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params, con
|
|
|
314
322
|
allDo.insert(allDo.end(), split.displayOnly.begin(), split.displayOnly.end());
|
|
315
323
|
|
|
316
324
|
std::vector<ColoredBody> bodies3d, sections2d;
|
|
317
|
-
for (
|
|
325
|
+
for (ColoredBody& c : split.foreground) {
|
|
318
326
|
// A body whose own Manifold::Status() isn't NoError (e.g.
|
|
319
327
|
// NonFiniteVertex, from a degenerate accumulated transform deep
|
|
320
328
|
// in an unrelated ancestor's positioning math -- found via a
|
|
@@ -331,7 +339,12 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params, con
|
|
|
331
339
|
// operand and unioning everything else is the closest match to
|
|
332
340
|
// its own behavior available without new numerical-robustness
|
|
333
341
|
// work on Manifold's own boolean ops.
|
|
334
|
-
if (c.body && c
|
|
342
|
+
if (c.body && bodyStatus(c) == manifold::Manifold::Error::NoError) bodies3d.push_back(c);
|
|
343
|
+
}
|
|
344
|
+
for (const ColoredBody& c : bodies3d) {
|
|
345
|
+
if (c.triColors) mixedColors = true;
|
|
346
|
+
if (!firstColor) firstColor = c.color;
|
|
347
|
+
else if (*firstColor != c.color) mixedColors = true;
|
|
335
348
|
}
|
|
336
349
|
for (const ColoredBody& c : split.foreground) {
|
|
337
350
|
if (c.section) sections2d.push_back(c);
|
|
@@ -358,17 +371,33 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params, con
|
|
|
358
371
|
if (!bodies3d.empty()) {
|
|
359
372
|
manifold::Manifold grp = *bodies3d.front().body;
|
|
360
373
|
for (size_t i = 1; i < bodies3d.size(); ++i) grp = grp + *bodies3d[i].body;
|
|
374
|
+
// The result stays LAZY inside Manifold: its emptiness is derived
|
|
375
|
+
// from the operands' flags here rather than asked, so a chain of
|
|
376
|
+
// unions under transforms is evaluated once, at the top, instead
|
|
377
|
+
// of once per level (see ColoredBody::knownEmpty).
|
|
378
|
+
const std::optional<bool> grpEmpty =
|
|
379
|
+
unionEmptyOf(bodies3d, [](const ColoredBody& b) -> const ColoredBody& { return b; });
|
|
380
|
+
const auto known = [](const std::optional<bool>& e, bool v) { return e && *e == v; };
|
|
361
381
|
if (!res3d) {
|
|
362
382
|
ColoredBody cb;
|
|
363
383
|
cb.body = std::move(grp);
|
|
364
384
|
cb.color = bodies3d.front().color;
|
|
385
|
+
cb.knownStatus = manifold::Manifold::Error::NoError; // every operand was
|
|
386
|
+
cb.knownEmpty = grpEmpty;
|
|
365
387
|
res3d = std::move(cb);
|
|
366
388
|
} else if (op == "union") {
|
|
367
389
|
res3d->body = *res3d->body + grp;
|
|
390
|
+
if (known(res3d->knownEmpty, false) || known(grpEmpty, false)) res3d->knownEmpty = false;
|
|
391
|
+
else if (known(res3d->knownEmpty, true) && known(grpEmpty, true)) res3d->knownEmpty = true;
|
|
392
|
+
else res3d->knownEmpty.reset();
|
|
368
393
|
} else if (op == "difference") {
|
|
369
394
|
res3d->body = *res3d->body - grp;
|
|
395
|
+
if (known(res3d->knownEmpty, true)) res3d->knownEmpty = true;
|
|
396
|
+
else if (!known(grpEmpty, true)) res3d->knownEmpty.reset(); // subtracting nothing changes nothing
|
|
370
397
|
} else if (op == "intersection") {
|
|
371
398
|
res3d->body = *res3d->body ^ grp;
|
|
399
|
+
if (known(res3d->knownEmpty, true) || known(grpEmpty, true)) res3d->knownEmpty = true;
|
|
400
|
+
else res3d->knownEmpty.reset();
|
|
372
401
|
}
|
|
373
402
|
}
|
|
374
403
|
if (!sections2d.empty()) {
|
|
@@ -394,7 +423,7 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params, con
|
|
|
394
423
|
}
|
|
395
424
|
}
|
|
396
425
|
|
|
397
|
-
if (res3d && res3d->body) attachTriColors(ev, *res3d);
|
|
426
|
+
if (res3d && res3d->body && mixedColors) attachTriColors(ev, *res3d);
|
|
398
427
|
|
|
399
428
|
std::vector<ColoredBody> result;
|
|
400
429
|
if (res3d) result.push_back(std::move(*res3d));
|
|
@@ -65,8 +65,8 @@ namespace {
|
|
|
65
65
|
// `union() { color("red") stroke(...); text(); }` had no red recorded for
|
|
66
66
|
// the stroke's runs at all, so every run looked identical, the merge kept
|
|
67
67
|
// the first child's colour, and the whole thing came out red.
|
|
68
|
-
void recordRunColors(Evaluator& ev,
|
|
69
|
-
if (!b.body || b
|
|
68
|
+
void recordRunColors(Evaluator& ev, ColoredBody& b, const std::optional<std::array<float, 4>>& rgba) {
|
|
69
|
+
if (!b.body || bodyIsEmpty(b)) return;
|
|
70
70
|
// A body that is still one original knows its own ID without building
|
|
71
71
|
// a mesh -- the common case, and the cheap one.
|
|
72
72
|
const int original = b.body->OriginalID();
|
|
@@ -68,6 +68,7 @@ ColoredBody combineBodies(const std::vector<ColoredBody>& bodies) {
|
|
|
68
68
|
ColoredBody cb;
|
|
69
69
|
cb.body = std::move(merged);
|
|
70
70
|
cb.color = bodies3d.front()->color;
|
|
71
|
+
cb.knownEmpty = unionEmptyOf(bodies3d, [](const ColoredBody* b) -> const ColoredBody& { return *b; });
|
|
71
72
|
return cb;
|
|
72
73
|
}
|
|
73
74
|
std::vector<const ColoredBody*> sections;
|
|
@@ -585,7 +585,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
|
|
|
585
585
|
ev.vmCallStack_.pop_back();
|
|
586
586
|
std::optional<Evaluator::UserCallHandle> bracket = std::move(ev.vmCallBrackets_.back());
|
|
587
587
|
ev.vmCallBrackets_.pop_back();
|
|
588
|
-
const std::string finishedName = finished->logicalName;
|
|
588
|
+
const std::string& finishedName = finished->logicalName; // `finished` outlives every use below
|
|
589
589
|
const bool ownsModuleSplice = finished->ownsModuleSplice;
|
|
590
590
|
const std::uint64_t moduleRandsBefore = finished->moduleRandsBefore;
|
|
591
591
|
const oscad::ASTNode* moduleSpliceCallNode = finished->moduleSpliceCallNode;
|
|
@@ -81,6 +81,7 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
|
|
|
81
81
|
insideHull = savedInsideHull;
|
|
82
82
|
|
|
83
83
|
applyDimensionRules(node);
|
|
84
|
+
++generatedNodeCount;
|
|
84
85
|
|
|
85
86
|
auto it = dispatch.find(node.kind);
|
|
86
87
|
if (node.isBuiltin && it != dispatch.end()) {
|
|
@@ -109,6 +110,25 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
|
|
|
109
110
|
manifoldCache_->put(*key, node.bodies);
|
|
110
111
|
if (!measuring_) cacheProducer_[*key] = node.node;
|
|
111
112
|
}
|
|
113
|
+
// The children's bodies have been consumed (by the dispatch above,
|
|
114
|
+
// or flattened into this node's own) and nothing reads them again:
|
|
115
|
+
// only a TOP-LEVEL node's bodies leave this function, a cache hit
|
|
116
|
+
// re-fills a node from the cache, and the CSG-tree facade in the
|
|
117
|
+
// bindings never used them. Keeping them held every intermediate
|
|
118
|
+
// result of the whole tree alive until the tree died -- one
|
|
119
|
+
// transformed copy per level per leaf. A cache, when there is one,
|
|
120
|
+
// already holds its own reference to whatever it wants to keep.
|
|
121
|
+
// The children's bodies have been consumed (by the dispatch above,
|
|
122
|
+
// or flattened into this node's own) and nothing reads them again:
|
|
123
|
+
// only a TOP-LEVEL node's bodies leave this function, a cache hit
|
|
124
|
+
// re-fills a node from the cache, and the CSG-tree facade in the
|
|
125
|
+
// bindings never used them. Keeping them held every intermediate
|
|
126
|
+
// result of the whole tree alive until the tree died -- one
|
|
127
|
+
// transformed copy per level per leaf. A cache, when there is one,
|
|
128
|
+
// already holds its own reference to whatever it wants to keep.
|
|
129
|
+
// Move-assign a fresh vector, not clear() or `= {}`: both keep the
|
|
130
|
+
// capacity, and 35K nodes' worth of 592-byte slots was 230MB.
|
|
131
|
+
for (const std::unique_ptr<CSGNode>& c : node.children) c->bodies = std::vector<ColoredBody>();
|
|
112
132
|
}
|
|
113
133
|
for (const ColoredBody& b : node.bodies) topLevelBodies.push_back(b);
|
|
114
134
|
}
|
|
@@ -151,8 +171,8 @@ DimRule dimRuleFor(const std::string& kind) {
|
|
|
151
171
|
return DimRule::Group;
|
|
152
172
|
}
|
|
153
173
|
|
|
154
|
-
bool isEmptyBody(
|
|
155
|
-
if (b.body) return b
|
|
174
|
+
bool isEmptyBody(ColoredBody& b) {
|
|
175
|
+
if (b.body) return bodyIsEmpty(b); // see ColoredBody::knownEmpty
|
|
156
176
|
if (b.section) return b.section->IsEmpty();
|
|
157
177
|
return true;
|
|
158
178
|
}
|
|
@@ -216,6 +216,8 @@ std::vector<std::unique_ptr<CSGNode>> Evaluator::resolveTreeImpl(const NodeList&
|
|
|
216
216
|
treeStack_.emplace_back();
|
|
217
217
|
rootCtx_ = &ctx;
|
|
218
218
|
lastCtx_ = &ctx;
|
|
219
|
+
usedFileCtx_.clear();
|
|
220
|
+
generatedNodeCount = 0;
|
|
219
221
|
// Cleared here, not just guarded by inResolvePass_ below, so a host
|
|
220
222
|
// that reuses one Evaluator across separate top-level resolveTree()/
|
|
221
223
|
// evaluate() calls (each against a freshly re-parsed AST -- the normal
|
|
@@ -87,6 +87,12 @@ ResolvedUseScopes resolveUseScopesInto(const std::vector<const oscad::ASTNode*>&
|
|
|
87
87
|
for (auto& sc : nested.usedFileScopes) result.usedFileScopes.push_back(std::move(sc));
|
|
88
88
|
result.usedFileScopes.push_back(std::move(nested.rootScope));
|
|
89
89
|
if (!libInjected.empty()) reanchor.emplace_back(std::move(libInjected), result.usedFileScopes.back().get());
|
|
90
|
+
for (auto& g : nested.usedFileGlobals) result.usedFileGlobals.push_back(std::move(g));
|
|
91
|
+
UsedFileGlobals globals{result.usedFileScopes.back().get(), {}};
|
|
92
|
+
for (const oscad::ASTNode* n : nested.ownNodesFiltered) {
|
|
93
|
+
if (n->kind() == oscad::NodeKind::Assignment) globals.assignments.push_back(n);
|
|
94
|
+
}
|
|
95
|
+
result.usedFileGlobals.push_back(std::move(globals));
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
for (const oscad::ASTNode* nodePtr : ownNodes) {
|
|
@@ -162,9 +162,44 @@ Value Evaluator::evalIdentifier(const std::string& name, const oscad::Position*
|
|
|
162
162
|
return Value{};
|
|
163
163
|
}
|
|
164
164
|
if (decl->kind() == oscad::NodeKind::ParameterDeclaration) return Value{};
|
|
165
|
+
// A file-level global (its declaring scope has no parent) is read back
|
|
166
|
+
// from its file's once-evaluated values, never re-evaluated -- see
|
|
167
|
+
// fileGlobal. Anything else (a module body's own variable reached from
|
|
168
|
+
// a function declared inside it) keeps the on-demand evaluation.
|
|
169
|
+
const oscad::Scope* declScope = ctx.scopeOf(*decl);
|
|
170
|
+
if (declScope && declScope->parent() == nullptr && rootCtx_) {
|
|
171
|
+
if (std::optional<const Value*> v = fileGlobal(*declScope, name)) {
|
|
172
|
+
if (*v) return **v;
|
|
173
|
+
if (warnIfUndef) warn("Ignoring unknown variable '" + name + "'", position);
|
|
174
|
+
return Value{};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
165
177
|
return evalExpr(*static_cast<const oscad::Assignment*>(decl)->expr, ctx);
|
|
166
178
|
}
|
|
167
179
|
|
|
180
|
+
std::optional<const Value*> Evaluator::fileGlobal(const oscad::Scope& fileRoot, const std::string& name) {
|
|
181
|
+
if (&fileRoot == rootCtx_->scope) return rootCtx_->let_->find(name);
|
|
182
|
+
auto it = usedFileCtx_.find(&fileRoot);
|
|
183
|
+
if (it == usedFileCtx_.end()) {
|
|
184
|
+
const UsedFileGlobals* globals = nullptr;
|
|
185
|
+
for (const UsedFileGlobals& g : usedFileGlobals_) {
|
|
186
|
+
if (g.root == &fileRoot) { globals = &g; break; }
|
|
187
|
+
}
|
|
188
|
+
if (!globals) return std::nullopt;
|
|
189
|
+
// Reference, not iterator: a global's initializer can call a function
|
|
190
|
+
// from ANOTHER used file, whose first read inserts here too, and a
|
|
191
|
+
// rehash invalidates iterators but never element references.
|
|
192
|
+
EvalContext& fctx = usedFileCtx_.emplace(&fileRoot, rootCtx_->callCtx(&fileRoot)).first->second;
|
|
193
|
+
// Source order, once; a read of a later global from an earlier one's
|
|
194
|
+
// initializer finds nothing yet and is undef, as in OpenSCAD.
|
|
195
|
+
for (const oscad::ASTNode* a : globals->assignments) {
|
|
196
|
+
evalAssignment(static_cast<const oscad::Assignment&>(*a), fctx);
|
|
197
|
+
}
|
|
198
|
+
return fctx.let_->find(name);
|
|
199
|
+
}
|
|
200
|
+
return it->second.let_->find(name);
|
|
201
|
+
}
|
|
202
|
+
|
|
168
203
|
// -- List comprehensions ----------------------------------------------
|
|
169
204
|
|
|
170
205
|
// Debug checkpoints below mirror the reference's _eval_list_comp AND its
|
|
@@ -251,9 +251,13 @@ EvalContext Evaluator::callCtxFor(const oscad::ASTNode& decl, EvalContext& ctx,
|
|
|
251
251
|
const oscad::Position& declPos = decl.position();
|
|
252
252
|
for (const auto& [activeDecl, count] : activeDeclRefcount_) {
|
|
253
253
|
const oscad::Position* outer = &activeDecl->position();
|
|
254
|
-
|
|
254
|
+
// Integer span test first: it rejects almost every candidate, and the
|
|
255
|
+
// origin compare it used to follow is a string compare per active
|
|
256
|
+
// declaration per call.
|
|
255
257
|
const bool sameSpan = (outer->start_offset == declPos.start_offset && outer->end_offset == declPos.end_offset);
|
|
256
|
-
if (
|
|
258
|
+
if (sameSpan || outer->start_offset > declPos.start_offset || declPos.end_offset > outer->end_offset) continue;
|
|
259
|
+
if (outer->origin != declPos.origin) continue;
|
|
260
|
+
{
|
|
257
261
|
if (usedChildCtx) *usedChildCtx = true;
|
|
258
262
|
return ctx.childCtx(scope, std::nullopt, std::move(childrenNodes), childrenCallerCtx);
|
|
259
263
|
}
|