openscad-cpp-evaluator 0.41.0__tar.gz → 0.42.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.
Files changed (207) hide show
  1. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/CLAUDE.md +4 -1
  2. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +4 -0
  4. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/csg_node.hpp +18 -0
  5. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/evaluator.hpp +29 -0
  6. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/pyproject.toml +1 -1
  7. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/booleans.cpp +7 -3
  8. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/control.cpp +1 -2
  9. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/registry.cpp +1 -1
  10. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/bytecode_vm.cpp +14 -4
  11. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/csg_resolve.cpp +6 -1
  12. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/expr_eval.cpp +8 -6
  13. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/user_calls.cpp +10 -2
  14. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/CMakeLists.txt +1 -0
  15. openscad_cpp_evaluator-0.42.0/tests/test_children_separate.cpp +278 -0
  16. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/.github/workflows/ci.yml +0 -0
  17. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/.github/workflows/wheels.yml +0 -0
  18. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/.gitignore +0 -0
  19. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/.gitmodules +0 -0
  20. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/CMakeLists.txt +0 -0
  21. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/LICENSE +0 -0
  22. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/README.md +0 -0
  23. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/bindings/CMakeLists.txt +0 -0
  24. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/bindings/ast_to_py.cpp +0 -0
  25. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/bindings/module.cpp +0 -0
  26. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/cmake/embed_font.cmake +0 -0
  27. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/examples/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/examples/eval_perf_benchmark.cpp +0 -0
  29. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/examples/manifold_cache_reuse.cpp +0 -0
  30. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/examples/minimal_debugger.cpp +0 -0
  31. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  32. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/.gitignore +0 -0
  33. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  34. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  35. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/README.md +0 -0
  36. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  37. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  38. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  39. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  40. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  41. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  42. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  43. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  44. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  45. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  46. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  47. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  48. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  49. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  50. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  51. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  52. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  53. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  54. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  55. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  56. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  57. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  58. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  59. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  60. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  61. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  62. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  63. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  64. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  65. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  66. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  67. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  68. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  69. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  70. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  71. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  72. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  73. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  74. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  75. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  76. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  77. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  78. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  79. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  80. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  81. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  82. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  83. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  84. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  85. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  86. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  87. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  88. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  89. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  90. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  91. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  92. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  93. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  94. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  95. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  96. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  97. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  98. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  99. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  100. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  101. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  102. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  103. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  104. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  105. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  106. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  107. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  108. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  109. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  110. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  111. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  112. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  113. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  114. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  116. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  117. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  118. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  119. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  120. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  121. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  122. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  123. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  124. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  125. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  126. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  127. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  128. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  129. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/resources/fonts/LICENSE +0 -0
  130. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  131. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/CMakeLists.txt +0 -0
  132. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/builtins.hpp +0 -0
  133. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/call_args.cpp +0 -0
  134. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/color.cpp +0 -0
  135. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/dxf_dim.cpp +0 -0
  136. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/extrude.cpp +0 -0
  137. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/function_builtins.cpp +0 -0
  138. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/import.cpp +0 -0
  139. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/modifiers.cpp +0 -0
  140. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/primitives_2d.cpp +0 -0
  141. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/primitives_3d.cpp +0 -0
  142. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/roof.cpp +0 -0
  143. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/segments.cpp +0 -0
  144. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/surface.cpp +0 -0
  145. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/text.cpp +0 -0
  146. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/topology.cpp +0 -0
  147. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/transforms.cpp +0 -0
  148. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/bytecode_compiler.cpp +0 -0
  149. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/colored_body.cpp +0 -0
  150. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/csg_generate.cpp +0 -0
  151. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/css_colors.cpp +0 -0
  152. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/debug/debug_repl.cpp +0 -0
  153. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/debug_profile.cpp +0 -0
  154. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/eval_context.cpp +0 -0
  155. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/eval_error.cpp +0 -0
  156. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/eval_use.cpp +0 -0
  157. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/export.cpp +0 -0
  158. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/import/dxf_import.cpp +0 -0
  159. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/import/mesh_import.cpp +0 -0
  160. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/import/surface_load.cpp +0 -0
  161. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/import/svg_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/manifold_cache.cpp +0 -0
  163. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/measure_geometry.cpp +0 -0
  164. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/mesh_check.cpp +0 -0
  165. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/stmt_eval.cpp +0 -0
  166. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/text/font_match.cpp +0 -0
  167. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/text/freetype_font_provider.cpp +0 -0
  168. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/text/text_metrics.cpp +0 -0
  169. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/value.cpp +0 -0
  170. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/src/zip_stored.cpp +0 -0
  171. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_booleans.cpp +0 -0
  172. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_bytecode_compiler.cpp +0 -0
  173. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_cli.cpp +0 -0
  174. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_control_flow.cpp +0 -0
  175. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_csg_tree.cpp +0 -0
  176. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_debug_hooks.cpp +0 -0
  177. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_dxf_svg_import.cpp +0 -0
  178. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_eval_context.cpp +0 -0
  179. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_export.cpp +0 -0
  180. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_expr_eval.cpp +0 -0
  181. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_extrude_roof.cpp +0 -0
  182. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_function_builtins.cpp +0 -0
  183. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_helpers.hpp +0 -0
  184. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_import_export.cpp +0 -0
  185. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_manifold_cache.cpp +0 -0
  186. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_mesh_check.cpp +0 -0
  187. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_multi_color_merge.cpp +0 -0
  188. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_primitives.cpp +0 -0
  189. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_profiling.cpp +0 -0
  190. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_python_bindings.py +0 -0
  191. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_render_expr.cpp +0 -0
  192. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_scope_trail.cpp +0 -0
  193. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_scoping.cpp +0 -0
  194. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_surface.cpp +0 -0
  195. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_tail_calls.cpp +0 -0
  196. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_text.cpp +0 -0
  197. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_transforms.cpp +0 -0
  198. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_unexpected_args.cpp +0 -0
  199. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_use.cpp +0 -0
  200. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_value.cpp +0 -0
  201. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_viewport_params.cpp +0 -0
  202. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tests/test_zip_stored.cpp +0 -0
  203. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tools/cli/CMakeLists.txt +0 -0
  204. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tools/cli/cli_lib.cpp +0 -0
  205. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tools/cli/cli_lib.hpp +0 -0
  206. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/tools/cli/main.cpp +0 -0
  207. {openscad_cpp_evaluator-0.41.0 → openscad_cpp_evaluator-0.42.0}/uv.lock +0 -0
@@ -481,7 +481,10 @@ grep for `ponytail:`.
481
481
  lexically nested inside an already-active call," picking `childCtx()` vs `callCtx()`
482
482
  accordingly; read its doc comment before touching it, it's the second-trickiest mechanism in this
483
483
  codebase after the CSG tree stack), `evalUserModule`/`evalUserFunction`/`evalFunctionLiteral`,
484
- and `builtinChildren` (children()/children(N), deferred evaluation). `evalFunctionCall`'s
484
+ and `builtinChildren` (children()/children(N), deferred evaluation; `children(separate=true)`
485
+ additionally marks each forwarded node `separateOperand` so the enclosing
486
+ union/difference/intersection treats them as separate operands rather than one grouped one --
487
+ a BelfrySCAD extension, see `Evaluator::appendGroupSizes`). `evalFunctionCall`'s
485
488
  precedence order (checked in this exact sequence): `import` (special-cased, its own
486
489
  module/expression-context split) → `isBuiltinFunctionName` (function_builtins.hpp — always wins
487
490
  if true) → user function lookup → function-literal *value* probe → "unknown function" warning.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.41.0
3
+ Version: 0.42.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -219,6 +219,10 @@ struct VmFrame {
219
219
  // mirrors evalModularCall's own `&node` (csg_resolve.cpp). Only
220
220
  // meaningful when ownsModuleSplice is true.
221
221
  const oscad::ASTNode* moduleSpliceCallNode = nullptr;
222
+ // children(separate=true) on the call this frame is forwarding for.
223
+ // Only meaningful while ownsModuleSplice is true, same as the two
224
+ // fields above; reset in releaseVmFrame for the same pooling reason.
225
+ bool separateChildren = false;
222
226
 
223
227
  // Whether callStack_.back() (at push time) genuinely IS this frame's
224
228
  // own logical call, and therefore safe for a tail hop inside this
@@ -34,6 +34,16 @@ 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
+ // Set only by children(separate=true): this node begins its OWN operand
38
+ // group in an enclosing union/difference/intersection/intersection_for,
39
+ // instead of merging into the single group its enclosing statement
40
+ // would otherwise form. See Evaluator::appendGroupSizes.
41
+ //
42
+ // Deliberately absent from cacheKey()'s allowlist (manifold_cache.cpp):
43
+ // it changes the PARENT's hashed "group_sizes" param, never this node's
44
+ // own geometry, so the parent already re-keys and this node must not.
45
+ bool separateOperand = false;
46
+
37
47
  // The call site that entered this node's call chain from the top
38
48
  // level, captured at RESOLVE time -- non-owning, AST-lifetime-bound
39
49
  // like `node`. nullptr for a node resolved at top level.
@@ -73,4 +83,12 @@ struct CSGNode {
73
83
  std::optional<std::string> cachedKey;
74
84
  };
75
85
 
86
+ // Marks every node from `from` onward as starting its own operand group --
87
+ // what children(separate=true) does to the geometry it just forwarded.
88
+ // A free function, not a member, so all three splice sites (user_calls.cpp
89
+ // and bytecode_vm.cpp's two) can reach it from this header alone.
90
+ inline void markSeparateOperands(std::vector<std::unique_ptr<CSGNode>>& nodes, size_t from) {
91
+ for (size_t i = from; i < nodes.size(); ++i) nodes[i]->separateOperand = true;
92
+ }
93
+
76
94
  } // namespace oscadeval
@@ -190,6 +190,30 @@ public:
190
190
  // statement.
191
191
  size_t currentTreeFrameSize() const { return treeStack_.back().size(); }
192
192
 
193
+ // Records the group(s) one child statement contributed, given the frame
194
+ // size captured before it ran. Normally that is exactly one group, of
195
+ // however many nodes the statement pushed -- including a group of size
196
+ // ZERO for a statement that produced no geometry (a disabled `*cube()`),
197
+ // which generateCsg relies on to reset intersection() and to bail
198
+ // difference(). Only children(separate=true) produces more than one:
199
+ // each node it marked starts a fresh group, so its forwarded children
200
+ // reach the enclosing operator as separate operands.
201
+ //
202
+ // Shared by all four group builders -- resolveCsg, resolveIntersectionFor
203
+ // and the VM's Op::CsgGroupEnd (which serves both) -- because
204
+ // "group_sizes" is otherwise built twice, once per engine, and would
205
+ // drift.
206
+ void appendGroupSizes(std::vector<Value>& groupSizes, size_t before) const {
207
+ const std::vector<std::unique_ptr<CSGNode>>& frame = treeStack_.back();
208
+ size_t start = before;
209
+ for (size_t i = before + 1; i < frame.size(); ++i) {
210
+ if (!frame[i]->separateOperand) continue;
211
+ groupSizes.push_back(Value{static_cast<double>(i - start)});
212
+ start = i;
213
+ }
214
+ groupSizes.push_back(Value{static_cast<double>(frame.size() - start)});
215
+ }
216
+
193
217
  // Generate pass: walks `tree` bottom-up (children before their own
194
218
  // node), calling each node's registered GenerateFn (falling back to
195
219
  // concatenating children's bodies for a kind with none registered).
@@ -342,6 +366,11 @@ public:
342
366
  struct ChildrenForward {
343
367
  EvalContext evalCtx;
344
368
  std::vector<const oscad::ASTNode*> nodes;
369
+ // children(separate=true): hand these to the enclosing union/
370
+ // difference/intersection as SEPARATE operands rather than as one
371
+ // grouped operand. Acted on after the nodes are evaluated, by
372
+ // marking the CSGNodes they produced -- see markSeparateOperands.
373
+ bool separate = false;
345
374
  };
346
375
  std::optional<ChildrenForward> prepareChildrenForward(const CallArgs& args, EvalContext& ctx);
347
376
 
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.41.0"
7
+ version = "0.42.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -68,7 +68,12 @@ 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. Mirrors _resolve_csg/_generate_csg,
71
+ // + attached children) as one operand.
72
+ //
73
+ // One statement can contribute MORE than one group: children(separate=true)
74
+ // marks the nodes it forwards so each starts its own operand group, which is
75
+ // how `difference() children(separate=true)` subtracts children 1..n from
76
+ // child 0. See Evaluator::appendGroupSizes, which both engines share. Mirrors _resolve_csg/_generate_csg,
72
77
  // minus _attach_tri_colors' multi-color-merge provenance (ponytail: a
73
78
  // merged body always takes its first contributing child's color, matching
74
79
  // this evaluator's own pre-tri_colors baseline behavior -- revisit
@@ -101,8 +106,7 @@ CSGParams resolveCsg(Evaluator& ev, const oscad::ModularCall& node, EvalContext&
101
106
  for (const oscad::ASTNode* geoNode : geoNodes) {
102
107
  const size_t before = ev.currentTreeFrameSize();
103
108
  ev.evalChildren(std::vector<const oscad::ASTNode*>{geoNode}, effCtx);
104
- const size_t after = ev.currentTreeFrameSize();
105
- groupSizes.push_back(Value{static_cast<double>(after - before)});
109
+ ev.appendGroupSizes(groupSizes, before);
106
110
  }
107
111
 
108
112
  CSGParams params;
@@ -119,8 +119,7 @@ CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersection
119
119
  if (!bodyNodes.empty()) ev.checkDebug(*bodyNodes.front(), parentCtx, /*forced=*/false, /*exprLevel=*/true);
120
120
  const size_t before = ev.currentTreeFrameSize();
121
121
  ev.evalChildren(bodyNodes, parentCtx);
122
- const size_t after = ev.currentTreeFrameSize();
123
- groupSizes.push_back(Value{static_cast<double>(after - before)});
122
+ ev.appendGroupSizes(groupSizes, before);
124
123
  return;
125
124
  }
126
125
  const auto& assign = node.assignments[depth];
@@ -112,7 +112,7 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
112
112
  {"intersection", {}},
113
113
  {"hull", {}},
114
114
  {"minkowski", {"convexity"}},
115
- {"children", {"index"}},
115
+ {"children", {"index", "separate"}},
116
116
  {"render", {"convexity"}},
117
117
  // "repair" is this port's own addition, not an upstream parameter.
118
118
  {"import",
@@ -360,7 +360,7 @@ void pushBracketedModuleFrame(Evaluator& ev, const CompiledChunk& chunk, const o
360
360
  // because module chunks never contain tail-call opcodes); don't inherit
361
361
  // a pooled function frame's stale true here either.
362
362
  void pushChildrenForwardFrame(Evaluator& ev, const CompiledChunk& chunk, EvalContext evalCtx,
363
- std::uint64_t randsBefore, const oscad::ASTNode& callNode) {
363
+ std::uint64_t randsBefore, const oscad::ASTNode& callNode, bool separate) {
364
364
  if (ev.vmCallStack_.size() >= Evaluator::kMaxVmCallStackDepth) {
365
365
  ev.error("Recursion too deep while forwarding children()", callNode);
366
366
  }
@@ -381,6 +381,7 @@ void pushChildrenForwardFrame(Evaluator& ev, const CompiledChunk& chunk, EvalCon
381
381
  frame->ownsModuleSplice = true;
382
382
  frame->moduleRandsBefore = randsBefore;
383
383
  frame->moduleSpliceCallNode = &callNode;
384
+ frame->separateChildren = separate;
384
385
  ev.vmCallStack_.push_back(std::move(frame));
385
386
  ev.vmCallBrackets_.emplace_back(std::nullopt);
386
387
  }
@@ -505,6 +506,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
505
506
  const bool ownsModuleSplice = finished->ownsModuleSplice;
506
507
  const std::uint64_t moduleRandsBefore = finished->moduleRandsBefore;
507
508
  const oscad::ASTNode* moduleSpliceCallNode = finished->moduleSpliceCallNode;
509
+ const bool separateChildren = finished->separateChildren;
508
510
  while (!finished->ctxChain.empty()) finished->ctxChain.pop_back();
509
511
  // Module frames never fire returnHook (native evalUserModule
510
512
  // never did either -- a module call has no "return value"
@@ -514,6 +516,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
514
516
  if (isModule && ownsModuleSplice) {
515
517
  std::vector<std::unique_ptr<CSGNode>> children = std::move(ev.treeStack_.back());
516
518
  ev.treeStack_.pop_back();
519
+ if (separateChildren) markSeparateOperands(children, 0);
517
520
  ev.spliceModuleChildren(std::move(children), moduleRandsBefore, *moduleSpliceCallNode);
518
521
  }
519
522
  if (isFloorFrame) {
@@ -1023,6 +1026,11 @@ Value driveVm(Evaluator& ev, size_t floor) {
1023
1026
  static_cast<const oscad::ModularCall*>(f.chunk->nativeStatements[static_cast<size_t>(ins.a)]);
1024
1027
  EvalContext scopedCtx = ctx.withScope(callNode->scope() ? callNode->scope() : ctx.scope);
1025
1028
  ev.checkDebug(*callNode, scopedCtx);
1029
+ // Same order as evalModularCall's own (csg_resolve.cpp):
1030
+ // warn, then resolve. Without this a children() typo
1031
+ // warns only under the interpreter, so no test for that
1032
+ // warning could run under both engines.
1033
+ warnUnexpectedBuiltinArgs(ev, *callNode);
1026
1034
  const std::uint64_t randsBefore = ev.randsCallCount();
1027
1035
  auto [args, effCtx] = resolveCallArgs(ev, callNode->arguments, scopedCtx);
1028
1036
  std::optional<Evaluator::ChildrenForward> fwd = ev.prepareChildrenForward(args, effCtx);
@@ -1039,9 +1047,11 @@ Value driveVm(Evaluator& ev, size_t floor) {
1039
1047
  const CompiledChunk* chunk = (ev.useBytecodeVm() && ev.inResolvePass())
1040
1048
  ? ev.lookupOrCompileChildrenListChunk(fwd->nodes)
1041
1049
  : nullptr;
1050
+ // Read before fwd->evalCtx is moved from.
1051
+ const bool separate = fwd->separate;
1042
1052
  if (chunk) {
1043
1053
  ev.treeStack_.emplace_back();
1044
- pushChildrenForwardFrame(ev, *chunk, std::move(fwd->evalCtx), randsBefore, *callNode);
1054
+ pushChildrenForwardFrame(ev, *chunk, std::move(fwd->evalCtx), randsBefore, *callNode, separate);
1045
1055
  // f.pc deliberately NOT advanced -- resumes when
1046
1056
  // the pushed frame completes; driveVm's completion
1047
1057
  // branch runs the splice (isModule &&
@@ -1065,6 +1075,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1065
1075
  }
1066
1076
  std::vector<std::unique_ptr<CSGNode>> children = std::move(ev.treeStack_.back());
1067
1077
  ev.treeStack_.pop_back();
1078
+ if (separate) markSeparateOperands(children, 0);
1068
1079
  ev.spliceModuleChildren(std::move(children), randsBefore, *callNode);
1069
1080
  ++f.pc;
1070
1081
  }
@@ -1304,8 +1315,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1304
1315
  }
1305
1316
  case Op::CsgGroupEnd: {
1306
1317
  PendingCsgWrap& pending = f.csgWrapStack.back();
1307
- const size_t after = ev.treeStack_.back().size();
1308
- pending.groupSizes.push_back(Value{static_cast<double>(after - pending.groupStartSize)});
1318
+ ev.appendGroupSizes(pending.groupSizes, pending.groupStartSize);
1309
1319
  ++f.pc;
1310
1320
  break;
1311
1321
  }
@@ -158,7 +158,12 @@ void Evaluator::spliceModuleChildren(std::vector<std::unique_ptr<CSGNode>> child
158
158
  // landed on.
159
159
  for (auto& c : children) c->uncacheable = true;
160
160
  }
161
- if (children.size() > 1) {
161
+ // children(separate=true) marked these to start their own operand
162
+ // groups, and the group walk only inspects the enclosing frame's top
163
+ // level -- so the wrapper would hide the marks. Splice instead.
164
+ const bool anySeparate =
165
+ std::any_of(children.begin(), children.end(), [](const auto& c) { return c->separateOperand; });
166
+ if (children.size() > 1 && !anySeparate) {
162
167
  auto unionNode = std::make_unique<CSGNode>();
163
168
  unionNode->kind = "union";
164
169
  unionNode->node = &callNode;
@@ -309,12 +309,14 @@ Value Evaluator::applyRange(const Value& startV, const Value& endV, const Value&
309
309
  // here, at construction, because that is where the reference reports it:
310
310
  // `r = [5:0];` warns even if nothing ever iterates r.
311
311
  //
312
- // Only an IMPLICIT step is checked. Writing the step out is taken as
313
- // deliberate, so [5:1:0] and [0:-1:5] stay silent -- a deliberate
314
- // divergence from the reference, which warns for those too. An implicit
315
- // step is always exactly 1, so the reference's other wording ("begin is
316
- // smaller than the end, but step is negative") cannot arise here and no
317
- // longer exists in this port.
312
+ // ponytail: only an IMPLICIT step is checked -- a deliberate divergence
313
+ // from the reference, which warns for [5:1:0] and [0:-1:5] as well.
314
+ // Writing the step out is taken as a statement of intent; the warning is
315
+ // for the author who wrote [5:0] meaning [5:-1:0] and got a silent empty
316
+ // loop. An implicit step is always exactly 1, so the reference's other
317
+ // wording ("begin is smaller than the end, but step is negative") cannot
318
+ // arise here and no longer exists in this port -- if it is ever needed
319
+ // again, this gate has been widened back to explicit steps.
318
320
  //
319
321
  // The epsilon matches the one the iteration path used before this check
320
322
  // moved here, so a range built from float arithmetic that lands a hair
@@ -179,6 +179,7 @@ void Evaluator::releaseVmFrame(std::unique_ptr<VmFrame> frame) {
179
179
  frame->ownsModuleSplice = false;
180
180
  frame->moduleRandsBefore = 0;
181
181
  frame->moduleSpliceCallNode = nullptr;
182
+ frame->separateChildren = false;
182
183
  vmFramePool_.push_back(std::move(frame));
183
184
  }
184
185
 
@@ -932,6 +933,11 @@ Value Evaluator::parentModuleName(int idx) const {
932
933
 
933
934
  std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(const CallArgs& args, EvalContext& ctx) {
934
935
  Value idxArg = getArg(args, 0, "index", Value{});
936
+ // Positional slot 1 is accepted as well as the name: adding "separate"
937
+ // to the builtin's parameter list already suppresses the "Too many
938
+ // unnamed arguments" warning for children(0, true), so reading it
939
+ // named-only would silently ignore an argument the author wrote.
940
+ const bool separate = truthy(getArg(args, 1, "separate", Value{false}));
935
941
  if (!ctx.childrenNodes || ctx.childrenNodes->empty()) return std::nullopt;
936
942
  const EvalContext* callerCtx = ctx.childrenCallerCtx;
937
943
  if (!callerCtx) return std::nullopt;
@@ -977,7 +983,7 @@ std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(cons
977
983
  }
978
984
 
979
985
  if (std::holds_alternative<std::monostate>(idxArg)) {
980
- return ChildrenForward{std::move(evalCtx), *ctx.childrenNodes};
986
+ return ChildrenForward{std::move(evalCtx), *ctx.childrenNodes, separate};
981
987
  }
982
988
 
983
989
  // children(N) indexes child *statements*, not output bodies -- a
@@ -1041,13 +1047,15 @@ std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(cons
1041
1047
  picked.push_back(geoNodes[static_cast<size_t>(idx)]);
1042
1048
  }
1043
1049
  if (picked.empty()) return std::nullopt;
1044
- return ChildrenForward{std::move(evalCtx), std::move(picked)};
1050
+ return ChildrenForward{std::move(evalCtx), std::move(picked), separate};
1045
1051
  }
1046
1052
 
1047
1053
  void Evaluator::builtinChildren(const CallArgs& args, EvalContext& ctx) {
1048
1054
  std::optional<ChildrenForward> fwd = prepareChildrenForward(args, ctx);
1049
1055
  if (!fwd) return;
1056
+ const size_t before = currentTreeFrameSize();
1050
1057
  evalChildren(fwd->nodes, fwd->evalCtx);
1058
+ if (fwd->separate) markSeparateOperands(treeStack_.back(), before);
1051
1059
  }
1052
1060
 
1053
1061
  } // namespace oscadeval
@@ -20,6 +20,7 @@ add_executable(oscad_eval_tests
20
20
  test_transforms.cpp
21
21
  test_render_expr.cpp
22
22
  test_booleans.cpp
23
+ test_children_separate.cpp
23
24
  test_control_flow.cpp
24
25
  test_tail_calls.cpp
25
26
  test_function_builtins.cpp
@@ -0,0 +1,278 @@
1
+ // children(separate=true) -- hand the forwarded children to the enclosing
2
+ // union/difference/intersection as SEPARATE operands instead of as the one
3
+ // grouped operand a statement normally contributes.
4
+ //
5
+ // Every test runs under BOTH engines. That is the standing rule in this
6
+ // repo, and here it is load-bearing rather than ceremonial: "group_sizes"
7
+ // is built in completely separate code per engine (resolveCsg in
8
+ // booleans.cpp vs Op::CsgGroupEnd in bytecode_vm.cpp), and the splice
9
+ // itself has three call sites across the two.
10
+
11
+ #include "openscad_cpp_evaluator/evaluator.hpp"
12
+
13
+ #include "test_helpers.hpp"
14
+
15
+ #include <gtest/gtest.h>
16
+
17
+ using namespace oscadeval;
18
+ using namespace oscadeval::test;
19
+
20
+ namespace {
21
+
22
+ // Same reasoning as test_csg_tree.cpp's own copy: the OSCAD_BYTECODE_VM env
23
+ // var is cached after its first read, so a test that must exercise a
24
+ // specific engine has to force it.
25
+ class ScopedVm {
26
+ public:
27
+ explicit ScopedVm(bool enabled) { Evaluator::setBytecodeVmEnabledForTesting(enabled); }
28
+ ~ScopedVm() { Evaluator::setBytecodeVmEnabledForTesting(std::nullopt); }
29
+ };
30
+
31
+ double totalVolume(const std::vector<ColoredBody>& bodies) {
32
+ double v = 0.0;
33
+ for (const ColoredBody& b : bodies) {
34
+ if (b.body) v += b.body->Volume();
35
+ }
36
+ return v;
37
+ }
38
+
39
+ // One big cube with two small cubes fully INSIDE it, disjoint from each
40
+ // other. Deliberate: the two subtrahends contribute nothing to a union, so
41
+ // the union answer (125000) and each partially-wrong grouping (124000) are
42
+ // all numerically distinct from the right one (123000). Overlapping shapes
43
+ // would have made several wrong answers land on the same number.
44
+ constexpr const char* kThreeChildren =
45
+ "{ cube(50, center=true);"
46
+ " translate([-15,0,0]) cube(10, center=true);"
47
+ " translate([ 15,0,0]) cube(10, center=true); }";
48
+
49
+ constexpr double kCube = 125000.0; // 50^3
50
+ constexpr double kSmall = 1000.0; // 10^3
51
+
52
+ } // namespace
53
+
54
+ // -- The feature ----------------------------------------------------------
55
+
56
+ TEST(ChildrenSeparate, DifferenceSubtractsEachForwardedChild) {
57
+ for (bool vm : {false, true}) {
58
+ ScopedVm guard(vm);
59
+ Evaluated e = evalSrc(std::string("module frame() { difference() children(separate=true); }\n"
60
+ "frame() ") + kThreeChildren);
61
+ EXPECT_NEAR(totalVolume(e.bodies), kCube - 2 * kSmall, 1e-6) << "vm=" << vm;
62
+ }
63
+ }
64
+
65
+ TEST(ChildrenSeparate, MatchesTheHandWrittenDifference) {
66
+ // The oracle: the same shape spelled out. These must agree exactly --
67
+ // that is the whole claim of the feature.
68
+ for (bool vm : {false, true}) {
69
+ ScopedVm guard(vm);
70
+ Evaluated sep = evalSrc(std::string("module frame() { difference() children(separate=true); }\n"
71
+ "frame() ") + kThreeChildren);
72
+ Evaluated hand = evalSrc(std::string("difference() ") + kThreeChildren);
73
+ EXPECT_NEAR(totalVolume(sep.bodies), totalVolume(hand.bodies), 1e-6) << "vm=" << vm;
74
+ }
75
+ }
76
+
77
+ TEST(ChildrenSeparate, WithoutTheFlagNothingChanges) {
78
+ // Pins the default. A regression here means every existing script that
79
+ // forwards children through a difference() silently changed meaning.
80
+ for (bool vm : {false, true}) {
81
+ ScopedVm guard(vm);
82
+ Evaluated e = evalSrc(std::string("module frame() { difference() children(); }\n"
83
+ "frame() ") + kThreeChildren);
84
+ EXPECT_NEAR(totalVolume(e.bodies), kCube, 1e-6) << "vm=" << vm;
85
+ }
86
+ }
87
+
88
+ TEST(ChildrenSeparate, IntersectionIntersectsEachForwardedChild) {
89
+ // The case with no workaround at all: for difference, A-(B|C) == A-B-C,
90
+ // so a union of the subtrahends happens to be right. For intersection
91
+ // it is not -- A&(B|C) != A&B&C.
92
+ for (bool vm : {false, true}) {
93
+ ScopedVm guard(vm);
94
+ const char* kids = "{ cube([10,10,10]); translate([5,0,0]) cube([10,10,10]); }";
95
+ Evaluated sep = evalSrc(std::string("module m() { intersection() children(separate=true); }\nm() ") + kids);
96
+ Evaluated off = evalSrc(std::string("module m() { intersection() children(); }\nm() ") + kids);
97
+ EXPECT_NEAR(totalVolume(sep.bodies), 500.0, 1e-6) << "vm=" << vm; // the overlap
98
+ EXPECT_NEAR(totalVolume(off.bodies), 1500.0, 1e-6) << "vm=" << vm; // the union
99
+ }
100
+ }
101
+
102
+ TEST(ChildrenSeparate, HonorsAnIndexSelection) {
103
+ // The 4th child would change the volume visibly if the selection leaked.
104
+ for (bool vm : {false, true}) {
105
+ ScopedVm guard(vm);
106
+ Evaluated e = evalSrc("module frame() { difference() children([0:2], separate=true); }\n"
107
+ "frame() { cube(50, center=true);"
108
+ " translate([-15,0,0]) cube(10, center=true);"
109
+ " translate([ 15,0,0]) cube(10, center=true);"
110
+ " translate([ 0,0,0]) cube(20, center=true); }");
111
+ EXPECT_NEAR(totalVolume(e.bodies), kCube - 2 * kSmall, 1e-6) << "vm=" << vm;
112
+ }
113
+ }
114
+
115
+ // -- Tree shape: the mechanism itself -------------------------------------
116
+
117
+ TEST(ChildrenSeparate, SplicesInsteadOfWrappingAndSplitsGroupSizes) {
118
+ for (bool vm : {false, true}) {
119
+ ScopedVm guard(vm);
120
+ Evaluated e = evalSrc(std::string("module frame() { difference() children(separate=true); }\n"
121
+ "frame() ") + kThreeChildren);
122
+ ASSERT_EQ(e.tree.size(), 1u) << "vm=" << vm;
123
+ const CSGNode& diff = *e.tree[0];
124
+ EXPECT_EQ(diff.kind, "difference") << "vm=" << vm;
125
+ // The grouping wrapper would hide the marks from the group walk, so
126
+ // it must not be there: three children, three groups of one.
127
+ ASSERT_EQ(diff.children.size(), 3u) << "vm=" << vm;
128
+ for (const auto& c : diff.children) {
129
+ EXPECT_NE(c->kind, "union") << "vm=" << vm;
130
+ }
131
+ const ListPtr* sizes = std::get_if<ListPtr>(&diff.params.at("group_sizes"));
132
+ ASSERT_NE(sizes, nullptr) << "vm=" << vm;
133
+ ASSERT_EQ((*sizes)->items.size(), 3u) << "vm=" << vm;
134
+ for (const Value& s : (*sizes)->items) EXPECT_EQ(std::get<double>(s), 1.0) << "vm=" << vm;
135
+ }
136
+ }
137
+
138
+ TEST(ChildrenSeparate, WithoutTheFlagStillWrapsIntoOneGroup) {
139
+ for (bool vm : {false, true}) {
140
+ ScopedVm guard(vm);
141
+ Evaluated e = evalSrc(std::string("module frame() { difference() children(); }\n"
142
+ "frame() ") + kThreeChildren);
143
+ ASSERT_EQ(e.tree.size(), 1u) << "vm=" << vm;
144
+ const CSGNode& diff = *e.tree[0];
145
+ ASSERT_EQ(diff.children.size(), 1u) << "vm=" << vm;
146
+ EXPECT_EQ(diff.children[0]->kind, "union") << "vm=" << vm;
147
+ EXPECT_FALSE(diff.children[0]->isBuiltin) << "vm=" << vm;
148
+ const ListPtr* sizes = std::get_if<ListPtr>(&diff.params.at("group_sizes"));
149
+ ASSERT_NE(sizes, nullptr) << "vm=" << vm;
150
+ ASSERT_EQ((*sizes)->items.size(), 1u) << "vm=" << vm;
151
+ EXPECT_EQ(std::get<double>((*sizes)->items[0]), 1.0) << "vm=" << vm;
152
+ }
153
+ }
154
+
155
+ // -- Degradation: it must not leak ----------------------------------------
156
+
157
+ TEST(ChildrenSeparate, UnderATransformDegradesToOneOperand) {
158
+ // The children land in translate()'s own frame, so difference() sees the
159
+ // single translate node. Nothing to separate -- the union is correct.
160
+ for (bool vm : {false, true}) {
161
+ ScopedVm guard(vm);
162
+ Evaluated e = evalSrc(std::string("module frame() { difference() translate([0,0,0]) children(separate=true); }\n"
163
+ "frame() ") + kThreeChildren);
164
+ EXPECT_NEAR(totalVolume(e.bodies), kCube, 1e-6) << "vm=" << vm;
165
+ }
166
+ }
167
+
168
+ TEST(ChildrenSeparate, DoesNotAffectALaterUnrelatedDifference) {
169
+ // A separated splice under a non-CSG parent must not colour the grouping
170
+ // of a difference() elsewhere in the same evaluation -- including one
171
+ // whose first statement happens to produce the same number of nodes.
172
+ for (bool vm : {false, true}) {
173
+ ScopedVm guard(vm);
174
+ Evaluated e = evalSrc("module m() { translate([200,0,0]) children(separate=true); }\n"
175
+ "m() { cube(1); cube(2); cube(3); }\n"
176
+ "difference() { cube(50, center=true); translate([-15,0,0]) cube(10, center=true); }");
177
+ // 1 + 8 + 27 from the spliced group, plus a normal difference.
178
+ EXPECT_NEAR(totalVolume(e.bodies), 36.0 + kCube - kSmall, 1e-6) << "vm=" << vm;
179
+ }
180
+ }
181
+
182
+ TEST(ChildrenSeparate, PropagatesThroughAUserModuleWrapper) {
183
+ // A judgement call, pinned so it cannot drift silently: the mark rides
184
+ // on the nodes, so a module whose whole body is children(separate=true)
185
+ // passes the separateness outward to whatever encloses the call.
186
+ // Retreating to "stop at the module boundary" is any_of -> all_of in
187
+ // spliceModuleChildren.
188
+ for (bool vm : {false, true}) {
189
+ ScopedVm guard(vm);
190
+ Evaluated e = evalSrc(std::string("module pass() { children(separate=true); }\n"
191
+ "difference() pass() ") + kThreeChildren);
192
+ EXPECT_NEAR(totalVolume(e.bodies), kCube - 2 * kSmall, 1e-6) << "vm=" << vm;
193
+ }
194
+ }
195
+
196
+ // -- Degenerate cases -----------------------------------------------------
197
+
198
+ TEST(ChildrenSeparate, OneChildIsASilentNoOp) {
199
+ for (bool vm : {false, true}) {
200
+ ScopedVm guard(vm);
201
+ std::vector<std::string> warnings;
202
+ Evaluated e = evalSrc("module frame() { difference() children(separate=true); }\n"
203
+ "frame() { cube(3); }",
204
+ [&](const std::string& m) { warnings.push_back(m); });
205
+ EXPECT_NEAR(totalVolume(e.bodies), 27.0, 1e-6) << "vm=" << vm;
206
+ EXPECT_TRUE(warnings.empty()) << "vm=" << vm;
207
+ }
208
+ }
209
+
210
+ TEST(ChildrenSeparate, NoChildrenIsASilentNoOp) {
211
+ for (bool vm : {false, true}) {
212
+ ScopedVm guard(vm);
213
+ std::vector<std::string> warnings;
214
+ Evaluated e = evalSrc("module frame() { difference() children(separate=true); }\nframe();",
215
+ [&](const std::string& m) { warnings.push_back(m); });
216
+ EXPECT_TRUE(e.bodies.empty()) << "vm=" << vm;
217
+ EXPECT_TRUE(warnings.empty()) << "vm=" << vm;
218
+ }
219
+ }
220
+
221
+ TEST(ChildrenSeparate, LeavesUnionAndHullUnchanged) {
222
+ // union() gives the same geometry either way, and hull()/minkowski()
223
+ // read bodies rather than groups -- the wrapper they used to receive was
224
+ // plain concatenation, so they already saw the children separately.
225
+ // This is the executable form of that claim.
226
+ for (bool vm : {false, true}) {
227
+ ScopedVm guard(vm);
228
+ for (const char* op : {"union", "hull"}) {
229
+ const std::string on = std::string("module m() { ") + op + "() children(separate=true); }\nm() ";
230
+ const std::string off = std::string("module m() { ") + op + "() children(); }\nm() ";
231
+ Evaluated a = evalSrc(on + kThreeChildren);
232
+ Evaluated b = evalSrc(off + kThreeChildren);
233
+ EXPECT_NEAR(totalVolume(a.bodies), totalVolume(b.bodies), 1e-6) << "vm=" << vm << " op=" << op;
234
+ }
235
+ }
236
+ }
237
+
238
+ // -- The argument itself --------------------------------------------------
239
+
240
+ TEST(ChildrenSeparate, TheArgumentIsAccepted) {
241
+ for (bool vm : {false, true}) {
242
+ ScopedVm guard(vm);
243
+ std::vector<std::string> warnings;
244
+ evalSrc("module m() { children(separate=true); }\nm() { cube(1); cube(2); }",
245
+ [&](const std::string& m) { warnings.push_back(m); });
246
+ EXPECT_TRUE(warnings.empty()) << "vm=" << vm;
247
+ }
248
+ }
249
+
250
+ TEST(ChildrenSeparate, AMisspelledArgumentStillWarnsUnderBothEngines) {
251
+ // Op::CallChildren did not call warnUnexpectedBuiltinArgs, so a typo was
252
+ // silent on the compiled path only. That gap is why this test can exist.
253
+ for (bool vm : {false, true}) {
254
+ ScopedVm guard(vm);
255
+ std::vector<std::string> warnings;
256
+ evalSrc("module m() { children(seperate=true); }\nm() { cube(1); cube(2); }",
257
+ [&](const std::string& m) { warnings.push_back(m); });
258
+ bool found = false;
259
+ for (const std::string& w : warnings) {
260
+ if (w.find("seperate") != std::string::npos) found = true;
261
+ }
262
+ EXPECT_TRUE(found) << "vm=" << vm << ", warnings=" << warnings.size();
263
+ }
264
+ }
265
+
266
+ TEST(ChildrenSeparate, AcceptsThePositionalSecondArgument) {
267
+ // Adding "separate" to the builtin's parameter list already silences the
268
+ // "Too many unnamed arguments" warning for this shape, so it must be
269
+ // read rather than silently ignored.
270
+ for (bool vm : {false, true}) {
271
+ ScopedVm guard(vm);
272
+ Evaluated e = evalSrc("module frame() { difference() children([0:2], true); }\n"
273
+ "frame() { cube(50, center=true);"
274
+ " translate([-15,0,0]) cube(10, center=true);"
275
+ " translate([ 15,0,0]) cube(10, center=true); }");
276
+ EXPECT_NEAR(totalVolume(e.bodies), kCube - 2 * kSmall, 1e-6) << "vm=" << vm;
277
+ }
278
+ }