openscad-cpp-evaluator 0.40.0__tar.gz → 0.50.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.40.0 → openscad_cpp_evaluator-0.50.0}/CLAUDE.md +136 -6
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/CMakeLists.txt +13 -1
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/bindings/module.cpp +34 -11
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +10 -2
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/ast/expression.cpp +4 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +4 -2
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +3 -1
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +13 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/bytecode.hpp +15 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/csg_node.hpp +2 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/evaluator.hpp +60 -1
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/export.hpp +7 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/value.hpp +1 -16
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/python/openscad_cpp_evaluator/__init__.py +19 -2
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/CMakeLists.txt +8 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/booleans.cpp +11 -6
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/builtins.hpp +5 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/control.cpp +5 -7
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/function_builtins.cpp +354 -7
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/registry.cpp +4 -1
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/topology.cpp +249 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/bytecode_compiler.cpp +56 -17
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/bytecode_vm.cpp +28 -5
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/csg_resolve.cpp +1 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/eval_context.cpp +28 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/export.cpp +89 -2
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/expr_eval.cpp +47 -5
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/stmt_eval.cpp +0 -2
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/user_calls.cpp +136 -25
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/value.cpp +6 -18
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/CMakeLists.txt +2 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_booleans.cpp +214 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_bytecode_compiler.cpp +7 -7
- openscad_cpp_evaluator-0.50.0/tests/test_children_separate.cpp +415 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_control_flow.cpp +41 -16
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_export.cpp +85 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_expr_eval.cpp +24 -11
- openscad_cpp_evaluator-0.50.0/tests/test_feature_detection.cpp +162 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_function_builtins.cpp +250 -11
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_use.cpp +6 -3
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/README.md +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.50.0}/uv.lock +0 -0
|
@@ -45,9 +45,13 @@ comprehension clause kinds, user function calls (recursion, defaults, `FunctionL
|
|
|
45
45
|
user module calls (`children()`/`children(N)`, closure detection via `callCtxFor`), list
|
|
46
46
|
indexing/swizzle member access; the full math/string/list/type-check builtin *function* dispatch
|
|
47
47
|
(`sin`/`cos`/`pow`/`search`/`lookup`/`chr`/`ord`/`is_*`/`object`/`has_key`/etc, ~43 names — a
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
same precedence rule as the builtin *modules* in `registry.cpp`: a user-defined function
|
|
49
|
+
**shadows** a same-named builtin, looked up *before* the builtin table. This was backwards until
|
|
50
|
+
0.47.0 — the port copied the old Python evaluator's `_eval_function_call` order, which had it
|
|
51
|
+
wrong; OpenSCAD 2026.02.01 echoes 999 for `function abs(x)=999; abs(-3)` and we echoed 3. Fixing
|
|
52
|
+
it needs **both** resolution sites: `evalFunctionCall` (`user_calls.cpp`) and the compiler's
|
|
53
|
+
call-site resolution (`bytecode_compiler.cpp`), which bakes builtin-vs-user in at compile time,
|
|
54
|
+
plus gating the silent `is_undef` probe on the call actually resolving to the builtin), STL/OBJ/OFF/3MF `import()` in both module (geometry) and expression
|
|
51
55
|
(VNF/JSON) contexts, and 3MF read/write; (Phase 6) `hull()`/`minkowski()` (transparent-splice
|
|
52
56
|
CSG-tree shape like union/difference/intersection, role-split via the shared `splitByRole`),
|
|
53
57
|
`linear_extrude()`/`rotate_extrude()`/`projection(cut=)`, and `roof()`; and (Phase 7) `offset()`,
|
|
@@ -441,6 +445,111 @@ handling in arithmetic, the closure-detection rule for `call_ctx` vs `child_ctx`
|
|
|
441
445
|
verbatim port, not an independent design. Deliberate divergences are called out in code comments —
|
|
442
446
|
grep for `ponytail:`.
|
|
443
447
|
|
|
448
|
+
- **Feature detection** — `$_SUPPORTED_FEATURE` (just `true`; names the CAPABILITY rather than the
|
|
449
|
+
vendor, so any evaluator adding `supported_feature()` is meant to set it, and checking it first
|
|
450
|
+
solves the bootstrapping problem of calling a function you don't know exists. Written
|
|
451
|
+
`!is_undef($_SUPPORTED_FEATURE) && supported_feature(...)` the guard is **silent** in OpenSCAD --
|
|
452
|
+
`is_undef()` is the one way to read an unknown variable there without a warning, and `&&`
|
|
453
|
+
short-circuits past the unknown call; verified against the reference), `$_BELFRYSCAD` (seeded in `EvalContext::makeRoot`, `src/eval_context.cpp`, as
|
|
454
|
+
`[major, minor, patch]` from `OSCAD_EVAL_VERSION_*` compile definitions that the top-level
|
|
455
|
+
`CMakeLists.txt` parses out of `pyproject.toml`, so wheel and local builds cannot disagree and a
|
|
456
|
+
bump needs one edit) and `supported_feature("name")` (`featureLevels()` in
|
|
457
|
+
`src/builtins/function_builtins.cpp`, returning a LEVEL so a feature whose semantics change can
|
|
458
|
+
be told from its earlier self; 0 for unknown names and non-string arguments, deliberately
|
|
459
|
+
silent so probing for a future feature is safe). Both exist because OpenSCAD **silently ignores**
|
|
460
|
+
unknown arguments -- `children(separate=true)` renders the wrong shape there with no warning --
|
|
461
|
+
and both read as `undef` in OpenSCAD, so a guard written against them works in both.
|
|
462
|
+
|
|
463
|
+
- **`levelset(field, bounds, isovalue)`** (`src/builtins/topology.cpp`, feature name `levelset`) —
|
|
464
|
+
a solid from a grid of sampled values: `field[i][j][k]` with i,j,k mapping to x,y,z. Wraps
|
|
465
|
+
`Manifold::LevelSet` with a C++ trilinear-sampling lambda; no new Manifold API.
|
|
466
|
+
**The field is a grid OR a `function(x,y,z)`, and the two trade off against each other** —
|
|
467
|
+
neither is simply better:
|
|
468
|
+
|
|
469
|
+
| | sampling | parallel | accuracy | ceiling |
|
|
470
|
+
|---|---|---|---|---|
|
|
471
|
+
| grid `field[i][j][k]` | 0.41 µs | **yes** | capped by the grid | ~550 MB at 200³ |
|
|
472
|
+
| `function(x,y,z)` | 0.96 µs | **no** | Manifold snaps toward the true surface | none |
|
|
473
|
+
|
|
474
|
+
Measured at matched resolution against the analytic sphere: **50³ grid 0.23 s (−0.244%) vs
|
|
475
|
+
function 0.32 s (−0.103%); 100³ grid 1.06 s (−0.059%) vs function 1.91 s (−0.025%)**. So the
|
|
476
|
+
function form is ~1.5× slower and ~2.4× more accurate. Both still beat BOSL2.
|
|
477
|
+
`canParallel` is the crux and is **not a tuning knob**: true for a grid because the lambda is
|
|
478
|
+
pure C++, false for a function because Manifold warns that parallel policies "will crash
|
|
479
|
+
language runtimes with runtime locks that expect to not be called back by unregistered threads".
|
|
480
|
+
The function form also needs `edge=` explicitly — there is no grid to infer spacing from and the
|
|
481
|
+
cost is cubic in it, so guessing would silently produce either a useless mesh or a ten-minute
|
|
482
|
+
one.
|
|
483
|
+
Measured against BOSL2's `isosurface.scad` (217 KB of marching cubes in script), same field:
|
|
484
|
+
**50³ 0.26 s vs 2.07 s (8.0×), 100³ 1.15 s vs 14.59 s (12.7×)**.
|
|
485
|
+
The trade is **memory**: ~67 bytes/cell, so 200³ ≈ 550 MB is the practical ceiling. A callback
|
|
486
|
+
samples lazily and has none.
|
|
487
|
+
**Accuracy is bounded by the grid, not by Manifold.** It samples on a body-centred cubic lattice
|
|
488
|
+
(`sdf.cpp:440-446`) so a plain cubic grid does not line up and the lambda interpolates — same
|
|
489
|
+
accuracy as grid-based marching cubes in script, worse than `LevelSet` given a true SDF. Hence
|
|
490
|
+
`tolerance = -1` is forced: a positive value makes Manifold do extra evaluations per vertex that
|
|
491
|
+
would only re-interpolate data already used. `edgeLength` defaults to the grid spacing for the
|
|
492
|
+
same reason.
|
|
493
|
+
Calling the field function from the generate pass has no live `EvalContext`, so a root is built
|
|
494
|
+
from the closure's own scope — `$`-variables sit at their defaults inside it.
|
|
495
|
+
Sign convention: Manifold takes positive as inside; a distance field is the other way round, so
|
|
496
|
+
the default flips it and `invert=true` flips back.
|
|
497
|
+
Two behaviours that look alike and are opposite: an isovalue **nothing reaches** gives empty
|
|
498
|
+
geometry; one **everything satisfies** gives the whole bounding box. Both are tested.
|
|
499
|
+
|
|
500
|
+
- **`linear_solve(A, b)`** (`src/builtins/function_builtins.cpp`, feature name `linear-solve`) —
|
|
501
|
+
returns `.x` (solution), `.det`, `.singular`. Dispatches on shape:
|
|
502
|
+
|
|
503
|
+
| shape | method | `.x` | `.det` |
|
|
504
|
+
|---|---|---|---|
|
|
505
|
+
| m = n | pivoted **LU** | the solution | product of U's diagonal, signed by row swaps |
|
|
506
|
+
| m > n | **Householder QR** | least squares | `undef` |
|
|
507
|
+
| m < n | **QR of Aᵀ** | minimum norm | `undef` |
|
|
508
|
+
|
|
509
|
+
Q is never formed. The reflectors are stored below the diagonal (v[0]
|
|
510
|
+
normalised to 1, scale in `taus`) and applied in forward order for `Qᵀb`,
|
|
511
|
+
reverse order for `Qy` — which is why this is far smaller and quicker than
|
|
512
|
+
BOSL2, whose QR materialises an m×m Q *and* an n×n permutation matrix and
|
|
513
|
+
then multiplies by it. **Minimum norm** comes from padding `y` with zeros to
|
|
514
|
+
length n before applying Q; a residual check alone will NOT catch getting
|
|
515
|
+
that wrong, since any solution passes it — assert the norm.
|
|
516
|
+
Rank deficiency without column pivoting cannot distinguish rank-deficient
|
|
517
|
+
from very ill-conditioned. It is a relative-tolerance heuristic on the
|
|
518
|
+
diagonal; say so rather than implying it is exact.
|
|
519
|
+
|
|
520
|
+
**Named to collide, deliberately.** BOSL2 defines `linear_solve()`, and since
|
|
521
|
+
0.47.0 a user function *shadows* a builtin — so this is unreachable from any
|
|
522
|
+
script that includes BOSL2 until BOSL2 drops its own. That is the intended
|
|
523
|
+
path (BelfrySCAD's author maintains BOSL2); callers then gain a `.x`, since
|
|
524
|
+
BOSL2's returns the bare solution and `[]` for singular. **Do not "fix" the
|
|
525
|
+
shadowing to make this reachable** — the shadowing is the correct, reference-
|
|
526
|
+
matching behaviour, and was itself a bug fix.
|
|
527
|
+
|
|
528
|
+
Measured per call, BOSL2 load subtracted, enough repetitions to clear noise
|
|
529
|
+
(harness checked: 200/2000/20000 reps at n=64 give 43/45/43 µs):
|
|
530
|
+
|
|
531
|
+
| n | BOSL2 `linear_solve` | ours | | n | BOSL2 `determinant` | ours |
|
|
532
|
+
|---:|---:|---:|---:|---:|---:|---:|
|
|
533
|
+
| 4 | 280 µs | 1.5 µs | 187× | 10 | 3.17 s | 0.33 s |
|
|
534
|
+
| 16 | 2.58 ms | 4.0 µs | 644× | 11 | 31.49 s | 0.33 s |
|
|
535
|
+
| 64 | 42.5 ms | 43 µs | 988× | | *cofactor expansion, O(n!)* | |
|
|
536
|
+
|
|
537
|
+
**Do not repeat the claim this replaces**: that BOSL2's solve "is not the
|
|
538
|
+
problem (128×128 in ~0.18s)". That was one-shot, with the solve lost inside
|
|
539
|
+
BOSL2's own load time. Keep the comparison fair though — BOSL2 does QR where
|
|
540
|
+
the square path here does LU, and it validates arguments every call.
|
|
541
|
+
Also correctness, not only speed: BOSL2's singularity test is a fixed
|
|
542
|
+
**absolute** `1e-9` on R's diagonal, unscaled by ‖A‖, so `2*I` scaled by
|
|
543
|
+
1e-10 is called singular there and solves correctly here.
|
|
544
|
+
|
|
545
|
+
**Profiling that shaped the scope** (don't redo it): NURBS *evaluation* is
|
|
546
|
+
0.6% linear algebra — basis eval 44%, type checks 25%. NURBS *interpolation*
|
|
547
|
+
is 35% at n=400, and every hot solve is **square**, so the QR paths are a
|
|
548
|
+
capability, not a measured win. `plane_intersection` (2×3, underdetermined)
|
|
549
|
+
is 0.1% of a `rounded_prism` model. The recurring cost across every workload
|
|
550
|
+
profiled is BOSL2's **argument type-checking**: 25–45%, millions of
|
|
551
|
+
`is_finite`/`is_num`/`is_nan` calls.
|
|
552
|
+
|
|
444
553
|
- `include/openscad_cpp_evaluator/value.hpp`, `src/value.cpp` — the `Value` variant (OpenSCAD's
|
|
445
554
|
dynamic value type) and its free-function arithmetic/comparison helpers. See the plan's §1 for
|
|
446
555
|
the type-mapping table and why there's no numpy-equivalent library.
|
|
@@ -481,7 +590,19 @@ grep for `ponytail:`.
|
|
|
481
590
|
lexically nested inside an already-active call," picking `childCtx()` vs `callCtx()`
|
|
482
591
|
accordingly; read its doc comment before touching it, it's the second-trickiest mechanism in this
|
|
483
592
|
codebase after the CSG tree stack), `evalUserModule`/`evalUserFunction`/`evalFunctionLiteral`,
|
|
484
|
-
and `builtinChildren` (children()/children(N), deferred evaluation).
|
|
593
|
+
and `builtinChildren` (children()/children(N), deferred evaluation).
|
|
594
|
+
**`children(..., separate=true)`** is a BelfrySCAD extension handled *before* any of that, by
|
|
595
|
+
`expandChildStatements`: a block is read with each separating forward replaced by one synthetic
|
|
596
|
+
`children(k)` statement per child it selects (`makeChildrenIndexCall`, owned by
|
|
597
|
+
`syntheticNodes_` and cleared with `idToNode` at `resolveTreeImpl` entry). Everything the
|
|
598
|
+
feature does follows from their being real statements -- `$children` counts them,
|
|
599
|
+
`children(i)` indexes them, and `union`/`difference`/`intersection` give each its own operand
|
|
600
|
+
group -- with no flag on `CSGNode` and nothing to leak. Called from `buildModuleChildCtx`,
|
|
601
|
+
`resolveCsg`, `resolveIntersectionFor`, and the VM's `Op::CsgGroupChildren`, which exists
|
|
602
|
+
because the compiler fixes group boundaries long before `$children` (and so the expansion size)
|
|
603
|
+
is knowable; it shares `expandSeparatingChildren` with the interpreter so the engines cannot
|
|
604
|
+
drift. Synthetic calls carry the author's own `Position` and no scope, so they resolve in the
|
|
605
|
+
evaluating context (`evalChildren`'s `runAll`). `evalFunctionCall`'s
|
|
485
606
|
precedence order (checked in this exact sequence): `import` (special-cased, its own
|
|
486
607
|
module/expression-context split) → `isBuiltinFunctionName` (function_builtins.hpp — always wins
|
|
487
608
|
if true) → user function lookup → function-literal *value* probe → "unknown function" warning.
|
|
@@ -684,8 +805,17 @@ grep for `ponytail:`.
|
|
|
684
805
|
— skip it and the mesh comes out non-manifold at the seam). Self-contained 2D geometry math here
|
|
685
806
|
(`dot2`/`len2`/`norm2`, the parabola-canonical-frame rotation in `discretizeArc`) exists nowhere
|
|
686
807
|
else in this codebase.
|
|
687
|
-
- `include/openscad_cpp_evaluator/export.hpp`, `src/export.cpp` —
|
|
688
|
-
|
|
808
|
+
- `include/openscad_cpp_evaluator/export.hpp`, `src/export.cpp` — the writers for all eight
|
|
809
|
+
formats `exportExtensions()` lists: 3MF, AMF, STL, OBJ, OFF, PLY, VRML, X3D (3MF via
|
|
810
|
+
`zip_stored.hpp`'s `writeDeflateZip`). **`exportExtensions()` is the one source of truth for
|
|
811
|
+
which formats exist** — the Python facade and BelfrySCAD's dialog/CLI all ask it rather than
|
|
812
|
+
restating the list, because three hand-written copies had already drifted far enough that `.off`
|
|
813
|
+
could be written by neither the GUI nor the CLI while every test passed.
|
|
814
|
+
Colour is carried by 3MF, AMF, OBJ (companion `.mtl`), PLY, VRML and X3D; STL and OFF compose
|
|
815
|
+
every body into one solid and store geometry only. **AMF** puts colour on the `<volume>` rather
|
|
816
|
+
than the face, so an object whose triangles are not all one colour is written as one volume per
|
|
817
|
+
colour — per-triangle `<color>` is legal in the spec but poorly supported by the slicers that are
|
|
818
|
+
the audience for it.
|
|
689
819
|
`colored_body.hpp`/`.cpp`'s `toRenderableBodies()` (thin-extrudes any top-level 2D-only result to
|
|
690
820
|
a 1e-3-unit-tall `flatPreview` Manifold) must run on a body list before handing it to any of these
|
|
691
821
|
writers — none of them look at `ColoredBody::section` at all, only `.body`. `tools/cli/cli_lib.cpp`
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
cmake_minimum_required(VERSION 3.24)
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
# Read the version straight out of pyproject.toml rather than repeating it
|
|
4
|
+
# here. It reaches C++ as OSCAD_EVAL_VERSION_{MAJOR,MINOR,PATCH} (see
|
|
5
|
+
# src/CMakeLists.txt), which is what seeds $_BELFRYSCAD -- so a released
|
|
6
|
+
# wheel and a local `cmake --build` always report the same numbers, and a
|
|
7
|
+
# version bump needs one edit, not three.
|
|
8
|
+
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/pyproject.toml" _oscad_pyproject)
|
|
9
|
+
if(NOT _oscad_pyproject MATCHES "\nversion = \"([0-9]+)\.([0-9]+)\.([0-9]+)\"")
|
|
10
|
+
message(FATAL_ERROR "could not read version = \"X.Y.Z\" from pyproject.toml")
|
|
11
|
+
endif()
|
|
12
|
+
set(OSCAD_EVAL_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
|
|
13
|
+
|
|
14
|
+
project(openscad_cpp_evaluator VERSION ${OSCAD_EVAL_VERSION} LANGUAGES CXX)
|
|
3
15
|
|
|
4
16
|
# On Apple Silicon, warn (don't fail -- a universal or intentionally-x86_64
|
|
5
17
|
# build is legitimate) if the effective target architecture is x86_64 while
|
|
@@ -385,17 +385,36 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
|
|
|
385
385
|
{
|
|
386
386
|
nb::gil_scoped_release rel;
|
|
387
387
|
auto logFn = [&echoes](const std::string& m) { echoes.push_back(m); };
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
388
|
+
try {
|
|
389
|
+
std::vector<std::unique_ptr<oscad::ASTNode>> ast = oscad::getASTFromFile(path);
|
|
390
|
+
oscadeval::ResolvedUseScopes used = oscadeval::resolveUseScopes(ast, path, logFn);
|
|
391
|
+
oscadeval::Evaluator ev(logFn, nullptr, manifoldCache, oscadeval::DebugHooks{}, profile);
|
|
392
|
+
oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
|
|
393
|
+
bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp));
|
|
394
|
+
collectIdSpans(ev, idSpans);
|
|
395
|
+
csgTree = std::move(ev.csgTree);
|
|
396
|
+
profileResult = std::move(ev.profileResult);
|
|
397
|
+
{
|
|
398
|
+
nb::gil_scoped_acquire g; // building Python objects needs the GIL back
|
|
399
|
+
std::tie(dyn, dynExplicit) = dynStateToPy(ctx);
|
|
400
|
+
}
|
|
401
|
+
} catch (const std::exception& e) {
|
|
402
|
+
// `echoes` is a local, so letting this unwind past here loses
|
|
403
|
+
// every echo and warning the script produced BEFORE it failed --
|
|
404
|
+
// which is exactly the output you want when something fails. The
|
|
405
|
+
// batching that makes them cheap (no GIL round-trip per line) is
|
|
406
|
+
// also what made them disposable.
|
|
407
|
+
//
|
|
408
|
+
// Carry them out on the exception itself: args = (message,
|
|
409
|
+
// [echo, ...]). The facade replays them through echo_fn and then
|
|
410
|
+
// re-raises. Anything that only looks at str(e) is unaffected,
|
|
411
|
+
// since args[0] is still the formatted diagnostic.
|
|
412
|
+
nb::gil_scoped_acquire g;
|
|
413
|
+
nb::list partial;
|
|
414
|
+
for (const std::string& s : echoes) partial.append(s);
|
|
415
|
+
nb::object args = nb::make_tuple(nb::str(e.what()), partial);
|
|
416
|
+
PyErr_SetObject(PyExc_RuntimeError, args.ptr());
|
|
417
|
+
throw nb::python_error();
|
|
399
418
|
}
|
|
400
419
|
}
|
|
401
420
|
|
|
@@ -767,6 +786,10 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
|
|
|
767
786
|
"logging them. Raises RuntimeError when there is no geometry, the format is unknown, or the "
|
|
768
787
|
"file cannot be opened.");
|
|
769
788
|
|
|
789
|
+
m.def("export_extensions", []() { return oscadeval::exportExtensions(); },
|
|
790
|
+
"The file extensions export_model understands, dot-prefixed. Read from the C++ writer table "
|
|
791
|
+
"rather than restated here, so the Python side cannot drift from what can actually be written.");
|
|
792
|
+
|
|
770
793
|
m.def("evaluate", &evaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("manifold_cache") = nullptr,
|
|
771
794
|
nb::arg("profile") = false,
|
|
772
795
|
"Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit).");
|
|
@@ -80,14 +80,22 @@ public:
|
|
|
80
80
|
class RangeLiteral : public Primary {
|
|
81
81
|
public:
|
|
82
82
|
RangeLiteral(Position position, std::unique_ptr<Expression> start, std::unique_ptr<Expression> end,
|
|
83
|
-
std::unique_ptr<Expression> step)
|
|
83
|
+
std::unique_ptr<Expression> step, bool implicitStep = false)
|
|
84
84
|
: Primary(NodeKind::RangeLiteral, std::move(position)), start(std::move(start)), end(std::move(end)),
|
|
85
|
-
step(std::move(step)) {}
|
|
85
|
+
step(std::move(step)), implicitStep(implicitStep) {}
|
|
86
86
|
|
|
87
87
|
std::unique_ptr<Expression> start;
|
|
88
88
|
std::unique_ptr<Expression> end;
|
|
89
89
|
std::unique_ptr<Expression> step;
|
|
90
90
|
|
|
91
|
+
// True when the source wrote the two-argument form `[a:b]` and the step
|
|
92
|
+
// node below is the literal 1.0 this parser synthesized for it. Consumers
|
|
93
|
+
// that only need the value can ignore this and read `step` as always
|
|
94
|
+
// present; it exists because "the author did not choose a step" is not
|
|
95
|
+
// recoverable from the synthesized node, and the evaluator's backwards-range
|
|
96
|
+
// warning fires only for that case (an explicit step is taken as deliberate).
|
|
97
|
+
bool implicitStep = false;
|
|
98
|
+
|
|
91
99
|
std::string toString() const override;
|
|
92
100
|
void buildScope(Scope& parentScope) override;
|
|
93
101
|
};
|
|
@@ -22,6 +22,10 @@ std::string NumberLiteral::toString() const {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
std::string RangeLiteral::toString() const {
|
|
25
|
+
// Print back the form that was written. Emitting the synthesized step for
|
|
26
|
+
// a two-argument range would round-trip `[5:0]` into `[5 : 1 : 0]`, which
|
|
27
|
+
// reads identically but suppresses the evaluator's backwards-range warning.
|
|
28
|
+
if (implicitStep) return "[" + start->toString() + " : " + end->toString() + "]";
|
|
25
29
|
return "[" + start->toString() + " : " + step->toString() + " : " + end->toString() + "]";
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -46,11 +46,13 @@ NodePtr makeUndefinedLiteral(ParserDriver& driver, const OscadLocation& loc) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
NodePtr makeRangeLiteral(ParserDriver& driver, const OscadLocation& loc, NodePtr start, NodePtr end, NodePtr step) {
|
|
49
|
-
|
|
49
|
+
const bool implicitStep = !step;
|
|
50
|
+
if (implicitStep) {
|
|
50
51
|
step = std::make_unique<NumberLiteral>(driver.toPosition(loc), 1.0);
|
|
51
52
|
}
|
|
52
53
|
return std::make_unique<RangeLiteral>(driver.toPosition(loc), nodeCast<Expression>(std::move(start)),
|
|
53
|
-
nodeCast<Expression>(std::move(end)), nodeCast<Expression>(std::move(step))
|
|
54
|
+
nodeCast<Expression>(std::move(end)), nodeCast<Expression>(std::move(step)),
|
|
55
|
+
implicitStep);
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
NodePtr makePositionalArgument(ParserDriver& driver, const OscadLocation& loc, NodePtr expr) {
|
|
@@ -112,6 +112,7 @@ json toJsonImpl(const ASTNode& node, bool includePos) {
|
|
|
112
112
|
j["start"] = valueToJson(n.start.get(), includePos);
|
|
113
113
|
j["end"] = valueToJson(n.end.get(), includePos);
|
|
114
114
|
j["step"] = valueToJson(n.step.get(), includePos);
|
|
115
|
+
if (n.implicitStep) j["implicitStep"] = true;
|
|
115
116
|
break;
|
|
116
117
|
}
|
|
117
118
|
case NodeKind::ParameterDeclaration: {
|
|
@@ -410,7 +411,8 @@ const std::unordered_map<std::string, Builder>& registry() {
|
|
|
410
411
|
{"RangeLiteral",
|
|
411
412
|
[](const json& j, Position pos) -> std::unique_ptr<ASTNode> {
|
|
412
413
|
return std::make_unique<RangeLiteral>(std::move(pos), childFromJson<Expression>(j, "start"),
|
|
413
|
-
childFromJson<Expression>(j, "end"), childFromJson<Expression>(j, "step")
|
|
414
|
+
childFromJson<Expression>(j, "end"), childFromJson<Expression>(j, "step"),
|
|
415
|
+
j.value("implicitStep", false));
|
|
414
416
|
}},
|
|
415
417
|
{"ParameterDeclaration",
|
|
416
418
|
[](const json& j, Position pos) -> std::unique_ptr<ASTNode> {
|
|
@@ -515,3 +515,16 @@ TEST(PrettyPrint, AssignmentRhsWithLeadingCommentAndTrailingLineComment) {
|
|
|
515
515
|
// no other translation unit -- including this test file -- can name them.
|
|
516
516
|
// Left as a known, permanent coverage ceiling rather than weakening the
|
|
517
517
|
// encapsulation just to reach 100%.
|
|
518
|
+
|
|
519
|
+
// The two-argument range keeps its shape through a print/reparse cycle.
|
|
520
|
+
// It would be easy to always print the synthesized step, and the result
|
|
521
|
+
// would still be a correct program -- but `[5:0]` and `[5:1:0]` mean
|
|
522
|
+
// different things to the evaluator's backwards-range warning, so
|
|
523
|
+
// reformatting a file must not quietly convert one into the other.
|
|
524
|
+
TEST(PrettyPrint, ImplicitRangeStepSurvivesRoundTrip) {
|
|
525
|
+
auto ast = parseAst("a = [5:0];\nb = [5:1:0];\n");
|
|
526
|
+
const std::string printed = toOpenscad(ast);
|
|
527
|
+
EXPECT_NE(printed.find("[5 : 0]"), std::string::npos) << printed;
|
|
528
|
+
EXPECT_NE(printed.find("[5 : 1 : 0]"), std::string::npos) << printed;
|
|
529
|
+
expectStablePrint("a = [5:0];\nb = [5:1:0];\n");
|
|
530
|
+
}
|
|
@@ -426,6 +426,21 @@ enum class Op {
|
|
|
426
426
|
// own `groupSizes.push_back(Value{...})` loop.
|
|
427
427
|
CsgGroupEnd,
|
|
428
428
|
|
|
429
|
+
// One `children(..., separate=true)` statement inside a CSG bracket.
|
|
430
|
+
// `a` interns the ModularCall in nativeStatements.
|
|
431
|
+
//
|
|
432
|
+
// A statement of its own, rather than a CsgGroupStart/End pair around
|
|
433
|
+
// compiled code, because how many groups it contributes is only known
|
|
434
|
+
// at RUNTIME -- `children([2:$children-1], separate=true)` reads
|
|
435
|
+
// $children, and the compiler emits its group boundaries long before
|
|
436
|
+
// any of that exists. So this op expands the call (the same
|
|
437
|
+
// Evaluator::expandSeparatingChildren the interpreter uses, so the two
|
|
438
|
+
// engines cannot drift) and records one group per expanded statement.
|
|
439
|
+
//
|
|
440
|
+
// Not an interpreter bail: the forwarded statements go through
|
|
441
|
+
// evalChildren, which self-gates and retries its own compiled chunks.
|
|
442
|
+
CsgGroupChildren,
|
|
443
|
+
|
|
429
444
|
// Closes the matching Op::PushCsgWrap bracket: pops
|
|
430
445
|
// VmFrame::csgWrapStack's own top entry FIRST (before anything that can
|
|
431
446
|
// itself throw, e.g. setTreeDepthOrThrow below -- same "exception-
|
|
@@ -34,6 +34,7 @@ struct CSGNode {
|
|
|
34
34
|
CSGParams params; // resolve step's plain-data output
|
|
35
35
|
bool uncacheable = false; // set by a later phase (ManifoldCache, Phase 8)
|
|
36
36
|
|
|
37
|
+
|
|
37
38
|
// The call site that entered this node's call chain from the top
|
|
38
39
|
// level, captured at RESOLVE time -- non-owning, AST-lifetime-bound
|
|
39
40
|
// like `node`. nullptr for a node resolved at top level.
|
|
@@ -73,4 +74,5 @@ struct CSGNode {
|
|
|
73
74
|
std::optional<std::string> cachedKey;
|
|
74
75
|
};
|
|
75
76
|
|
|
77
|
+
|
|
76
78
|
} // namespace oscadeval
|
|
@@ -190,6 +190,7 @@ public:
|
|
|
190
190
|
// statement.
|
|
191
191
|
size_t currentTreeFrameSize() const { return treeStack_.back().size(); }
|
|
192
192
|
|
|
193
|
+
|
|
193
194
|
// Generate pass: walks `tree` bottom-up (children before their own
|
|
194
195
|
// node), calling each node's registered GenerateFn (falling back to
|
|
195
196
|
// concatenating children's bodies for a kind with none registered).
|
|
@@ -342,9 +343,56 @@ public:
|
|
|
342
343
|
struct ChildrenForward {
|
|
343
344
|
EvalContext evalCtx;
|
|
344
345
|
std::vector<const oscad::ASTNode*> nodes;
|
|
346
|
+
// The `separate` flag as written at the call. Read by
|
|
347
|
+
// expandSeparatingChildren, which turns the forward into one
|
|
348
|
+
// statement per child; builtinChildren itself ignores it, since by
|
|
349
|
+
// the time a forward runs the expansion has already happened.
|
|
350
|
+
bool separate = false;
|
|
351
|
+
// Which of the caller's geometry statements `nodes` came from, in
|
|
352
|
+
// the same order. Needed by expandChildStatements, which turns each
|
|
353
|
+
// one back into its own `children(k)` statement.
|
|
354
|
+
std::vector<size_t> pickedIndices;
|
|
345
355
|
};
|
|
346
356
|
std::optional<ChildrenForward> prepareChildrenForward(const CallArgs& args, EvalContext& ctx);
|
|
347
357
|
|
|
358
|
+
// A child-statement block with every `children(..., separate=true)`
|
|
359
|
+
// replaced by one synthetic `children(k)` statement per child it
|
|
360
|
+
// selects. That is the whole of what separate=true does: the forwarded
|
|
361
|
+
// children become real, countable statements at this call site, so
|
|
362
|
+
// $children sees them, children(i) indexes them, and a boolean operator
|
|
363
|
+
// gives each its own operand group -- all for free, because they ARE
|
|
364
|
+
// statements rather than nodes wearing a flag.
|
|
365
|
+
//
|
|
366
|
+
// Called wherever a block is consumed: a module call's children
|
|
367
|
+
// (buildModuleChildCtx), and union/difference/intersection/
|
|
368
|
+
// intersection_for's own child list.
|
|
369
|
+
std::vector<const oscad::ASTNode*> expandChildStatements(
|
|
370
|
+
const std::vector<std::unique_ptr<oscad::ASTNode>>& block, EvalContext& ctx);
|
|
371
|
+
|
|
372
|
+
// One synthetic `children(index)` statement, owned by syntheticNodes_.
|
|
373
|
+
const oscad::ASTNode* makeChildrenIndexCall(const oscad::ModularCall& origin, size_t index);
|
|
374
|
+
|
|
375
|
+
// The sole argument of `is_undef(x)` when x is a bare identifier, else
|
|
376
|
+
// nullptr. That call is a PROBE -- asking whether a name is defined must
|
|
377
|
+
// not itself complain that it isn't -- so the name is read with
|
|
378
|
+
// warnIfUndef=false. Static and syntactic so the bytecode compiler can
|
|
379
|
+
// ask at compile time and emit LoadFreeNoWarn.
|
|
380
|
+
static const oscad::Identifier* undefProbeTarget(const oscad::PrimaryCall& call);
|
|
381
|
+
|
|
382
|
+
// Syntactic gate: is this statement a `children(..., separate=true)`?
|
|
383
|
+
// Cheap and side-effect-free, so the bytecode compiler can ask it at
|
|
384
|
+
// COMPILE time to decide which opcode to emit, long before the flag's
|
|
385
|
+
// value (or the child count it selects) is knowable.
|
|
386
|
+
static bool isSeparatingChildrenCall(const oscad::ASTNode& stmt);
|
|
387
|
+
|
|
388
|
+
// The runtime half: the statements `call` expands to. nullopt means the
|
|
389
|
+
// `separate` flag resolved falsey after all, and the caller should run
|
|
390
|
+
// the original statement unchanged. Shared by expandChildStatements
|
|
391
|
+
// (interpreter) and Op::CsgGroupChildren (VM) so the engines cannot
|
|
392
|
+
// drift on what expansion means.
|
|
393
|
+
std::optional<std::vector<const oscad::ASTNode*>> expandSeparatingChildren(const oscad::ModularCall& call,
|
|
394
|
+
EvalContext& ctx);
|
|
395
|
+
|
|
348
396
|
// "WARNING: {message}{locSuffix(position)}" via echoFn_, no-op if unset.
|
|
349
397
|
// Public: builtins/import.cpp's not-manifold warning is emitted from a
|
|
350
398
|
// free function, same reasoning as tagGenerated()/builtinChildren().
|
|
@@ -392,7 +440,12 @@ public:
|
|
|
392
440
|
Value applyUnaryOp(oscad::NodeKind kind, const Value& v, const oscad::Position& pos);
|
|
393
441
|
Value applyIndexAccess(const Value& obj, const Value& idx);
|
|
394
442
|
Value applyMemberAccess(const Value& obj, const std::string& member);
|
|
395
|
-
|
|
443
|
+
// `implicitStep` is the RangeLiteral's own flag: true when the source
|
|
444
|
+
// wrote `[a:b]` and the parser supplied the 1.0. It only affects whether
|
|
445
|
+
// the backwards-range warning fires; the resulting value is the same
|
|
446
|
+
// either way. `pos` locates that warning.
|
|
447
|
+
Value applyRange(const Value& startV, const Value& endV, const Value& stepV, bool implicitStep = false,
|
|
448
|
+
const oscad::Position* pos = nullptr);
|
|
396
449
|
|
|
397
450
|
// Entry point for a call site resolved INSIDE compiled bytecode (the
|
|
398
451
|
// CALL_FN opcode, bytecode_vm.cpp): `bound` is already fully evaluated
|
|
@@ -1622,6 +1675,12 @@ public:
|
|
|
1622
1675
|
// is on top *at that moment* -- the frame this node's own build call
|
|
1623
1676
|
// pushed just before resolving. See csg_resolve.cpp for the full
|
|
1624
1677
|
// push/call/pop sequence.
|
|
1678
|
+
// Synthetic `children(k)` calls built by expandChildStatements. Owned
|
|
1679
|
+
// here because CSGNode::node and idToNode hold non-owning pointers into
|
|
1680
|
+
// them, so they must outlive the pass; cleared with those maps at
|
|
1681
|
+
// resolveTreeImpl entry.
|
|
1682
|
+
std::vector<std::unique_ptr<oscad::ASTNode>> syntheticNodes_;
|
|
1683
|
+
|
|
1625
1684
|
std::vector<std::vector<std::unique_ptr<CSGNode>>> treeStack_;
|
|
1626
1685
|
|
|
1627
1686
|
private:
|
|
@@ -97,6 +97,13 @@ void writeVrml(const std::string& path, const std::vector<ExportObject>& objects
|
|
|
97
97
|
// Color) and Shape level 1 (Appearance, Material), exactly the nodes used.
|
|
98
98
|
void writeX3d(const std::string& path, const std::vector<ExportObject>& objects);
|
|
99
99
|
|
|
100
|
+
// AMF 1.1. XML like X3D, but its colour lives on the <volume>, not the
|
|
101
|
+
// face: a mesh holds one vertex list and one <volume> per material. An
|
|
102
|
+
// object whose triangles are not all one colour therefore becomes several
|
|
103
|
+
// volumes -- which is what a multimaterial slicer reads as separate
|
|
104
|
+
// materials to assign to tools.
|
|
105
|
+
void writeAmf(const std::string& path, const std::vector<ExportObject>& objects);
|
|
106
|
+
|
|
100
107
|
// OFF (Object File Format): header "OFF", "$verts $tris 0", vertex lines,
|
|
101
108
|
// then "3 i j k" per triangle (0-indexed, count-prefixed). Mirrors
|
|
102
109
|
// export.py's write_off.
|
|
@@ -355,20 +355,6 @@ private:
|
|
|
355
355
|
// it, keeping expandIterable() itself free of any Evaluator/echo coupling.
|
|
356
356
|
using RangeTooManyFn = std::function<void(size_t count)>;
|
|
357
357
|
|
|
358
|
-
// Called when a range's step points AWAY from its end -- begin > end with a
|
|
359
|
-
// positive step, or begin < end with a negative step. Such a range is
|
|
360
|
-
// naturally empty, and the reference warns rather than iterating zero times
|
|
361
|
-
// in silence. `stepPositive` picks which of its two messages applies; use
|
|
362
|
-
// rangeDirectionWarning() to build the text.
|
|
363
|
-
//
|
|
364
|
-
// Separate from RangeTooManyFn for the same reason that one exists: it
|
|
365
|
-
// keeps expandIterable() free of any Evaluator/echo coupling.
|
|
366
|
-
using RangeDirectionFn = std::function<void(bool stepPositive)>;
|
|
367
|
-
|
|
368
|
-
// The reference's own wording, quoted verbatim, for a range whose step
|
|
369
|
-
// points away from its end. Shared so the five call sites cannot drift.
|
|
370
|
-
std::string rangeDirectionWarning(bool stepPositive);
|
|
371
|
-
|
|
372
358
|
// Converts a for()/intersection_for() loop assignment's evaluated RHS into
|
|
373
359
|
// the sequence of values to iterate: undef -> empty, range -> a LAZY
|
|
374
360
|
// sequence (matching OscRange's own start/step/end iteration, half-open
|
|
@@ -394,8 +380,7 @@ std::string rangeDirectionWarning(bool stepPositive);
|
|
|
394
380
|
// one with a live consumer (BelfrySCAD) that surfaced it. The count is
|
|
395
381
|
// computed in closed form (not IterableValues::size()'s O(n) walk) so a
|
|
396
382
|
// legitimate huge-but-under-the-limit range never pays an eager pass.
|
|
397
|
-
IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany = nullptr
|
|
398
|
-
const RangeDirectionFn& onWrongDirection = nullptr);
|
|
383
|
+
IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany = nullptr);
|
|
399
384
|
|
|
400
385
|
// `each <body>`'s own flatten-one-level rule, shared by the AST interpreter
|
|
401
386
|
// (evalListLiteral/evalListElement's ListCompEach handling, expr_eval.cpp)
|
|
@@ -315,8 +315,14 @@ def export_model(path: str, geometry, format: str = "", ascii_stl: bool = False,
|
|
|
315
315
|
|
|
316
316
|
|
|
317
317
|
def export_extensions() -> list:
|
|
318
|
-
"""The file extensions export_model understands, dot-prefixed.
|
|
319
|
-
|
|
318
|
+
"""The file extensions export_model understands, dot-prefixed.
|
|
319
|
+
|
|
320
|
+
Asked of the extension rather than restated here. The hardcoded copy
|
|
321
|
+
this replaces had already drifted: it was missing formats the C++
|
|
322
|
+
writer table had gained, and nothing failed -- callers simply could
|
|
323
|
+
not reach them.
|
|
324
|
+
"""
|
|
325
|
+
return list(_ext.export_extensions())
|
|
320
326
|
|
|
321
327
|
|
|
322
328
|
def check_mesh(verts, tris) -> dict:
|
|
@@ -504,6 +510,17 @@ class Evaluator:
|
|
|
504
510
|
except EvalError:
|
|
505
511
|
raise
|
|
506
512
|
except Exception as e: # ParseError/EvalError from C++ arrive as RuntimeError
|
|
513
|
+
# The binding attaches whatever the script echoed before it failed
|
|
514
|
+
# as args[1] (see bindings/module.cpp). Replay it first: a failed
|
|
515
|
+
# run's own output is the most useful thing it produced, and
|
|
516
|
+
# dropping it means the console shows the error with none of the
|
|
517
|
+
# context that led to it.
|
|
518
|
+
args = getattr(e, "args", ())
|
|
519
|
+
if len(args) == 2 and isinstance(args[1], list):
|
|
520
|
+
if self._echo_fn:
|
|
521
|
+
for line in args[1]:
|
|
522
|
+
self._echo_fn(line)
|
|
523
|
+
raise EvalError(str(args[0])) from e
|
|
507
524
|
raise EvalError(str(e)) from e
|
|
508
525
|
bodies = bodies_from_dicts(body_dicts)
|
|
509
526
|
# originalID -> a node with `.position` (start/end offsets) for WYSIWYG
|
|
@@ -46,6 +46,14 @@ add_library(openscad_cpp_evaluator STATIC
|
|
|
46
46
|
builtins/surface.cpp
|
|
47
47
|
)
|
|
48
48
|
|
|
49
|
+
# Seeds $_BELFRYSCAD (eval_context.cpp). Parsed from pyproject.toml by the
|
|
50
|
+
# top-level CMakeLists so there is one source of truth for the version.
|
|
51
|
+
target_compile_definitions(openscad_cpp_evaluator PRIVATE
|
|
52
|
+
OSCAD_EVAL_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}
|
|
53
|
+
OSCAD_EVAL_VERSION_MINOR=${PROJECT_VERSION_MINOR}
|
|
54
|
+
OSCAD_EVAL_VERSION_PATCH=${PROJECT_VERSION_PATCH}
|
|
55
|
+
)
|
|
56
|
+
|
|
49
57
|
target_include_directories(openscad_cpp_evaluator PUBLIC
|
|
50
58
|
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
|
|
51
59
|
)
|
|
@@ -68,7 +68,13 @@ std::optional<manifold::CrossSection> toCrossSection(const std::vector<ColoredBo
|
|
|
68
68
|
// operand (unioned together), and each subsequent statement's bodies are
|
|
69
69
|
// unioned then subtracted -- a flat evaluation would lose this grouping and
|
|
70
70
|
// misbehave when e.g. BOSL2's attachable() returns multiple bodies (parent
|
|
71
|
-
// + attached children) as one operand.
|
|
71
|
+
// + attached children) as one operand.
|
|
72
|
+
//
|
|
73
|
+
// One statement can still turn into several: children(separate=true) is
|
|
74
|
+
// expanded into one `children(k)` statement per child it forwards, before
|
|
75
|
+
// this loop ever sees the block (Evaluator::expandChildStatements), which is
|
|
76
|
+
// how `difference() children(separate=true)` subtracts children 1..n from
|
|
77
|
+
// child 0. Nothing special happens here -- they are simply statements. Mirrors _resolve_csg/_generate_csg,
|
|
72
78
|
// minus _attach_tri_colors' multi-color-merge provenance (ponytail: a
|
|
73
79
|
// merged body always takes its first contributing child's color, matching
|
|
74
80
|
// this evaluator's own pre-tri_colors baseline behavior -- revisit
|
|
@@ -87,11 +93,11 @@ CSGParams resolveCsg(Evaluator& ev, const oscad::ModularCall& node, EvalContext&
|
|
|
87
93
|
|
|
88
94
|
std::vector<const oscad::ASTNode*> assignNodes;
|
|
89
95
|
std::vector<const oscad::ASTNode*> geoNodes;
|
|
90
|
-
for (const
|
|
96
|
+
for (const oscad::ASTNode* c : ev.expandChildStatements(node.children, effCtx)) {
|
|
91
97
|
if (c->kind() == oscad::NodeKind::Assignment) {
|
|
92
|
-
assignNodes.push_back(c
|
|
98
|
+
assignNodes.push_back(c);
|
|
93
99
|
} else if (c->kind() != oscad::NodeKind::ModuleDeclaration && c->kind() != oscad::NodeKind::FunctionDeclaration) {
|
|
94
|
-
geoNodes.push_back(c
|
|
100
|
+
geoNodes.push_back(c);
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
if (!assignNodes.empty()) ev.evalChildren(assignNodes, effCtx);
|
|
@@ -101,8 +107,7 @@ CSGParams resolveCsg(Evaluator& ev, const oscad::ModularCall& node, EvalContext&
|
|
|
101
107
|
for (const oscad::ASTNode* geoNode : geoNodes) {
|
|
102
108
|
const size_t before = ev.currentTreeFrameSize();
|
|
103
109
|
ev.evalChildren(std::vector<const oscad::ASTNode*>{geoNode}, effCtx);
|
|
104
|
-
|
|
105
|
-
groupSizes.push_back(Value{static_cast<double>(after - before)});
|
|
110
|
+
groupSizes.push_back(Value{static_cast<double>(ev.currentTreeFrameSize() - before)});
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
CSGParams params;
|