openscad-cpp-evaluator 0.53.0__tar.gz → 0.55.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 (208) hide show
  1. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/evaluator.hpp +9 -0
  3. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/import.cpp +7 -4
  5. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/primitives_3d.cpp +14 -4
  6. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/topology.cpp +38 -3
  7. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/bytecode_compiler.cpp +20 -24
  8. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/csg_generate.cpp +6 -0
  9. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/stmt_eval.cpp +13 -13
  10. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_bytecode_compiler.cpp +11 -5
  11. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_control_flow.cpp +53 -0
  12. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_csg_tree.cpp +78 -0
  13. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_primitives.cpp +26 -0
  14. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/.github/workflows/ci.yml +0 -0
  15. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/.github/workflows/wheels.yml +0 -0
  16. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/.gitignore +0 -0
  17. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/.gitmodules +0 -0
  18. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/CLAUDE.md +0 -0
  19. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/LICENSE +0 -0
  21. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/README.md +0 -0
  22. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/bindings/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/bindings/ast_to_py.cpp +0 -0
  24. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/bindings/module.cpp +0 -0
  25. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/cmake/embed_font.cmake +0 -0
  26. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/examples/CMakeLists.txt +0 -0
  27. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/examples/eval_perf_benchmark.cpp +0 -0
  28. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/examples/manifold_cache_reuse.cpp +0 -0
  29. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/examples/minimal_debugger.cpp +0 -0
  30. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  31. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/.gitignore +0 -0
  32. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  33. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  34. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/README.md +0 -0
  35. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  36. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  37. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  38. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  39. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  40. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  41. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  42. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  43. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  44. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  45. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  46. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  47. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  48. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  49. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  50. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  51. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  52. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  53. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  54. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  55. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  56. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  57. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  58. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  59. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  60. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  61. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  62. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  63. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  64. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  65. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  66. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  67. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  68. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  69. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  70. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  71. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  72. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  73. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  74. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  75. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  76. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  77. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  78. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  79. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  80. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  81. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  82. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  83. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  84. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  85. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  86. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  87. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  88. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  89. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  90. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  91. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  92. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  93. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  94. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  95. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  96. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  97. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  98. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  99. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  100. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  101. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  102. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  103. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  104. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  105. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  106. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  107. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  108. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  109. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  110. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  111. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  112. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  113. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  114. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  116. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  117. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  118. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  119. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  120. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  121. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  122. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  123. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  124. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  125. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  126. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  127. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  128. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  129. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  130. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/resources/fonts/LICENSE +0 -0
  131. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  132. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/CMakeLists.txt +0 -0
  133. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/booleans.cpp +0 -0
  134. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/builtins.hpp +0 -0
  135. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/call_args.cpp +0 -0
  136. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/color.cpp +0 -0
  137. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/control.cpp +0 -0
  138. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/dxf_dim.cpp +0 -0
  139. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/extrude.cpp +0 -0
  140. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/function_builtins.cpp +0 -0
  141. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/modifiers.cpp +0 -0
  142. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/primitives_2d.cpp +0 -0
  143. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/registry.cpp +0 -0
  144. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/roof.cpp +0 -0
  145. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/segments.cpp +0 -0
  146. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/surface.cpp +0 -0
  147. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/text.cpp +0 -0
  148. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/builtins/transforms.cpp +0 -0
  149. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/bytecode_vm.cpp +0 -0
  150. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/colored_body.cpp +0 -0
  151. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/csg_resolve.cpp +0 -0
  152. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/css_colors.cpp +0 -0
  153. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/debug/debug_repl.cpp +0 -0
  154. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/debug_profile.cpp +0 -0
  155. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/eval_context.cpp +0 -0
  156. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/eval_error.cpp +0 -0
  157. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/eval_use.cpp +0 -0
  158. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/export.cpp +0 -0
  159. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/expr_eval.cpp +0 -0
  160. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/import/dxf_import.cpp +0 -0
  161. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/import/mesh_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/import/surface_load.cpp +0 -0
  163. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/import/svg_import.cpp +0 -0
  164. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/manifold_cache.cpp +0 -0
  165. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/measure_geometry.cpp +0 -0
  166. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/mesh_check.cpp +0 -0
  167. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/text/font_match.cpp +0 -0
  168. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/text/freetype_font_provider.cpp +0 -0
  169. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/text/text_metrics.cpp +0 -0
  170. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/user_calls.cpp +0 -0
  171. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/value.cpp +0 -0
  172. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/src/zip_stored.cpp +0 -0
  173. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/CMakeLists.txt +0 -0
  174. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_booleans.cpp +0 -0
  175. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_children_separate.cpp +0 -0
  176. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_cli.cpp +0 -0
  177. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_debug_hooks.cpp +0 -0
  178. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_dxf_svg_import.cpp +0 -0
  179. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_eval_context.cpp +0 -0
  180. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_export.cpp +0 -0
  181. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_expr_eval.cpp +0 -0
  182. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_extrude_roof.cpp +0 -0
  183. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_feature_detection.cpp +0 -0
  184. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_function_builtins.cpp +0 -0
  185. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_helpers.hpp +0 -0
  186. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_import_export.cpp +0 -0
  187. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_manifold_cache.cpp +0 -0
  188. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_mesh_check.cpp +0 -0
  189. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_multi_color_merge.cpp +0 -0
  190. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_profiling.cpp +0 -0
  191. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_python_bindings.py +0 -0
  192. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_render_expr.cpp +0 -0
  193. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_scope_trail.cpp +0 -0
  194. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_scoping.cpp +0 -0
  195. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_surface.cpp +0 -0
  196. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_tail_calls.cpp +0 -0
  197. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_text.cpp +0 -0
  198. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_transforms.cpp +0 -0
  199. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_unexpected_args.cpp +0 -0
  200. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_use.cpp +0 -0
  201. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_value.cpp +0 -0
  202. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_viewport_params.cpp +0 -0
  203. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tests/test_zip_stored.cpp +0 -0
  204. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tools/cli/CMakeLists.txt +0 -0
  205. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tools/cli/cli_lib.cpp +0 -0
  206. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tools/cli/cli_lib.hpp +0 -0
  207. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/tools/cli/main.cpp +0 -0
  208. {openscad_cpp_evaluator-0.53.0 → openscad_cpp_evaluator-0.55.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.53.0
3
+ Version: 0.55.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -294,6 +294,15 @@ public:
294
294
  // depth-first on one thread.
295
295
  const oscad::Position* generateWarnEntry = nullptr;
296
296
 
297
+ // Set while generating anything beneath a hull(). An open mesh there is
298
+ // not a mistake to report: a convex hull needs only points, so BOSL2's
299
+ // hull_points(pts, fast=true) hands polyhedron() a deliberately garbage
300
+ // face list purely to carry the vertices in. The reference says nothing
301
+ // about it, and spheroid(circum=true, style="icosa") goes through that
302
+ // path -- warning would fire on an everyday BOSL2 call. Covers the whole
303
+ // subtree, not just direct children, since hull() consumes all of it.
304
+ bool insideHull = false;
305
+
297
306
  ColoredBody tagGenerated(manifold::Manifold body, const oscad::ASTNode& node, const Value& colorValue);
298
307
 
299
308
  // tagGenerated()'s counterpart for a mesh Manifold refused to build --
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.53.0"
7
+ version = "0.55.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -318,10 +318,13 @@ std::vector<ColoredBody> generateImport(Evaluator& ev, const CSGParams& params,
318
318
  const MeshDiagnosis d = checkMesh(mesh);
319
319
  std::string why = d.summary();
320
320
  if (why.empty()) why = manifoldErrorName(body.Status());
321
- ev.warn("import: mesh is not a closed solid (" + why +
322
- "); drawing it as a surface, but it cannot take part in any CSG "
323
- "operation" + (repair ? "" : ". Try import(..., repair=true)"),
324
- &node.position());
321
+ if (!ev.insideHull) {
322
+ ev.warn("import: mesh is not a closed solid (" + why +
323
+ "); drawing it as a surface. hull() can still use its points, but "
324
+ "it cannot take part in union/difference/intersection" +
325
+ std::string(repair ? "" : ". Try import(..., repair=true)"),
326
+ &node.position());
327
+ }
325
328
  // tagDisplayOnly carries a raw triangle soup for the renderer to
326
329
  // draw, and the renderer is float either way, so narrowing here
327
330
  // costs nothing this mesh will ever be measured on.
@@ -781,6 +781,14 @@ std::vector<ColoredBody> generatePolyhedron(Evaluator& ev, const CSGParams& para
781
781
  for (const Value& t : std::get<ListPtr>(params.at("tris"))->items) {
782
782
  mesh.triVerts.push_back(static_cast<uint64_t>(std::get<double>(t)));
783
783
  }
784
+ // No faces at all is empty geometry, not a failure. BOSL2's debug_vnf()
785
+ // passes [verts, []] to draw vertex labels with no surface, and real
786
+ // OpenSCAD accepts `polyhedron(points=..., faces=[])` silently. Checked
787
+ // before the status branches below, which would otherwise report the
788
+ // empty mesh as NotManifold and discard it with a warning.
789
+ if (mesh.triVerts.empty()) {
790
+ return {ev.tagGenerated(manifold::Manifold(), node, params.at("color"))};
791
+ }
784
792
  manifold::Manifold body(mesh);
785
793
  if (isDrawableFailure(body, mesh)) {
786
794
  // An open surface -- faces that don't close the solid. Manifold
@@ -794,10 +802,12 @@ std::vector<ColoredBody> generatePolyhedron(Evaluator& ev, const CSGParams& para
794
802
  // closed mesh with reversed winding, non-manifold vertices or
795
803
  // self-intersections builds perfectly well, and only an OPEN one
796
804
  // reaches this branch. The count points straight at the problem.
797
- ev.warn("polyhedron: mesh is not closed -- " + std::to_string(countBoundaryEdges(mesh)) +
798
- " boundary edge(s); drawing it as a surface, but it cannot "
799
- "take part in any CSG operation",
800
- &node.position());
805
+ if (!ev.insideHull) {
806
+ ev.warn("polyhedron: mesh is not closed -- " + std::to_string(countBoundaryEdges(mesh)) +
807
+ " boundary edge(s); drawing it as a surface. hull() can still "
808
+ "use its points, but it cannot take part in union/difference/intersection",
809
+ &node.position());
810
+ }
801
811
  manifold::MeshGL soup;
802
812
  soup.numProp = 3;
803
813
  soup.vertProperties.assign(mesh.vertProperties.begin(), mesh.vertProperties.end());
@@ -35,19 +35,51 @@ CSGParams resolveHull(Evaluator& ev, const oscad::ModularCall& node, EvalContext
35
35
  // matching the reference's own `if bodies_3d: ... else: sections...`
36
36
  // either/or dispatch). background/highlight/show_only bodies pass through
37
37
  // untouched, same as union/difference/intersection.
38
+ // Vertex positions out of a MeshGL, whatever else it carries per vertex.
39
+ // Only the first three properties are the position; numProp may be larger.
40
+ static void appendVertices(const manifold::MeshGL& mesh, std::vector<manifold::vec3>& out) {
41
+ const size_t stride = mesh.numProp;
42
+ if (stride < 3) return;
43
+ for (size_t i = 0; i + 2 < mesh.vertProperties.size(); i += stride)
44
+ out.push_back({mesh.vertProperties[i], mesh.vertProperties[i + 1],
45
+ mesh.vertProperties[i + 2]});
46
+ }
47
+
38
48
  std::vector<ColoredBody> generateHull(Evaluator&, const CSGParams&, const std::vector<std::unique_ptr<CSGNode>>& children,
39
49
  const oscad::ASTNode&) {
40
50
  const std::vector<ColoredBody> bodies = flattenCsgTree(children);
41
51
  if (bodies.empty()) return {};
42
52
  const RoleSplit split = splitByRole(bodies);
43
53
 
54
+ // An open mesh has no Manifold, but a convex hull only ever needed its
55
+ // POINTS -- closedness is irrelevant to one. BOSL2 depends on this:
56
+ // hull_points(pts, fast=true) hands polyhedron() a deliberately garbage
57
+ // face list purely to carry the vertices into hull(), and
58
+ // spheroid(circum=true, style="icosa") goes through it. Dropping those
59
+ // children left the loose triangles to fall through un-hulled, which is
60
+ // what the reference does not do.
61
+ std::vector<manifold::vec3> loosePoints;
62
+ for (const ColoredBody& c : split.displayOnly) {
63
+ if (c.rawMesh) appendVertices(*c.rawMesh, loosePoints);
64
+ }
65
+
44
66
  std::optional<ColoredBody> hullResult;
45
- if (!split.foreground.empty()) {
67
+ if (!split.foreground.empty() || !loosePoints.empty()) {
46
68
  std::vector<manifold::Manifold> bodies3d;
47
69
  for (const ColoredBody& c : split.foreground) {
48
70
  if (c.body) bodies3d.push_back(*c.body);
49
71
  }
50
- if (!bodies3d.empty()) {
72
+ if (!loosePoints.empty()) {
73
+ // Hulling points and bodies together has to go through the
74
+ // point overload, so the solids contribute their vertices too.
75
+ for (const manifold::Manifold& m : bodies3d)
76
+ appendVertices(m.GetMeshGL(), loosePoints);
77
+ ColoredBody cb;
78
+ cb.body = manifold::Manifold::Hull(loosePoints);
79
+ cb.color = split.foreground.empty() ? split.displayOnly.front().color
80
+ : split.foreground.front().color;
81
+ hullResult = std::move(cb);
82
+ } else if (!bodies3d.empty()) {
51
83
  ColoredBody cb;
52
84
  cb.body = manifold::Manifold::Hull(bodies3d);
53
85
  cb.color = split.foreground.front().color;
@@ -71,7 +103,10 @@ std::vector<ColoredBody> generateHull(Evaluator&, const CSGParams&, const std::v
71
103
  result.insert(result.end(), split.background.begin(), split.background.end());
72
104
  result.insert(result.end(), split.highlight.begin(), split.highlight.end());
73
105
  result.insert(result.end(), split.showOnly.begin(), split.showOnly.end());
74
- result.insert(result.end(), split.displayOnly.begin(), split.displayOnly.end());
106
+ // Only pass the open meshes through if they were NOT hulled; hull()
107
+ // consumes its children, so re-adding them would draw them twice.
108
+ if (loosePoints.empty())
109
+ result.insert(result.end(), split.displayOnly.begin(), split.displayOnly.end());
75
110
  return result;
76
111
  }
77
112
 
@@ -1564,35 +1564,31 @@ public:
1564
1564
  return;
1565
1565
  }
1566
1566
  case NodeKind::ModularLet: {
1567
- // Mirrors Evaluator::evalLetBlock exactly (stmt_eval.cpp):
1568
- // every assignment's RHS is evaluated against the
1569
- // ORIGINAL (parent) ctx, never an earlier sibling's own
1570
- // write in THIS SAME let-block (a documented, deliberate
1571
- // divergence from the let-EXPRESSION form's sequential
1572
- // visibility) -- so every RHS is compiled+evaluated FIRST,
1573
- // all left on f.stack, and only written into the freshly-
1574
- // opened child scope (Op::OpenLetScope) AFTER every one of
1575
- // them has already run against the still-current PARENT
1576
- // ctx. No outer statement-level check for the let-block
1577
- // node itself (matches evalChildren's own ModularLet
1578
- // exclusion) -- each assignment gets its own instead,
1579
- // exactly like evalLetBlock's own per-assignment
1580
- // checkDebug. The body (n.children) compiles inline,
1581
- // recursively, against the now-current child ctx -- same
1582
- // Compiler instance, so nested statements get every bit of
1583
- // this same real-bytecode treatment too.
1567
+ // Mirrors Evaluator::evalLetBlock (stmt_eval.cpp): the
1568
+ // child scope opens FIRST, then each assignment's RHS is
1569
+ // compiled and stored in source order, so a later binding
1570
+ // sees an earlier one -- `let(a=1, b=a+1) ...` binds b to
1571
+ // 2, exactly as the let-EXPRESSION form does.
1572
+ //
1573
+ // This used to compile every RHS before Op::OpenLetScope
1574
+ // and then store them back-to-front, deliberately denying
1575
+ // that visibility. Real OpenSCAD grants it, and BOSL2
1576
+ // depends on it (isosurface.scad chains five bindings where
1577
+ // each uses the previous).
1578
+ //
1579
+ // No outer statement-level check for the let-block node
1580
+ // itself (matches evalChildren's own ModularLet exclusion)
1581
+ // -- each assignment gets its own, exactly like
1582
+ // evalLetBlock's own per-assignment checkDebug. The body
1583
+ // (n.children) compiles inline, recursively, against the
1584
+ // now-current child ctx.
1584
1585
  auto& n = static_cast<const oscad::ModularLet&>(stmt);
1585
1586
  CompileScope exprScope;
1587
+ out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1586
1588
  for (const auto& assign : n.assignments) {
1587
1589
  out.push_back({Op::CheckDebugStatement, internNativeStatement(assign.get()), 0, nullptr});
1588
1590
  compileIsolatedExpr(*assign->expr, out, exprScope);
1589
- }
1590
- out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1591
- // Values pop in REVERSE push order (LIFO) -- iterate the
1592
- // assignment list backwards so each Op::StoreLetVar is
1593
- // paired with the value that was actually pushed for IT.
1594
- for (auto it = n.assignments.rbegin(); it != n.assignments.rend(); ++it) {
1595
- out.push_back({Op::StoreLetVar, internName((*it)->name->name), 0, &(*it)->position()});
1591
+ out.push_back({Op::StoreLetVar, internName(assign->name->name), 0, &assign->position()});
1596
1592
  }
1597
1593
  compileStatementList(n.children, out);
1598
1594
  out.push_back({Op::CloseExprScope, 0, 0, nullptr});
@@ -64,7 +64,13 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
64
64
  std::vector<CSGNode*> childPtrs;
65
65
  childPtrs.reserve(node.children.size());
66
66
  for (const std::unique_ptr<CSGNode>& c : node.children) childPtrs.push_back(c.get());
67
+ // Saved and restored rather than left set, exactly as
68
+ // generateWarnEntry is: a sibling generated afterwards is not
69
+ // inside this hull.
70
+ const bool savedInsideHull = insideHull;
71
+ if (node.isBuiltin && node.kind == "hull") insideHull = true;
67
72
  generateTreeImpl(childPtrs);
73
+ insideHull = savedInsideHull;
68
74
 
69
75
  applyDimensionRules(node);
70
76
 
@@ -155,20 +155,20 @@ void Evaluator::evalFor(const oscad::ModularFor& node, EvalContext& ctx) {
155
155
 
156
156
  void Evaluator::evalLetBlock(const oscad::ModularLet& node, EvalContext& ctx) {
157
157
  EvalContext childCtx = ctx.childCtx(nullptr, std::nullopt, ctx.childrenNodes, ctx.childrenCallerCtx);
158
- // ponytail/port-fidelity note: each assignment's RHS is evaluated
159
- // against the ORIGINAL `ctx`, not the growing `childCtx` -- unlike the
160
- // let() *expression* form's documented sequential visibility
161
- // (`let(a=1, b=a+1) expr` sees `a` while evaluating `b`), the
162
- // *statement* form `let(a=1, b=a+1) { ... }` does NOT: `b`'s `a+1`
163
- // looks `a` up in `ctx`, which doesn't have this let's own `a` binding
164
- // yet. This is a direct, deliberate port of the reference's
165
- // _eval_let_block, not an oversight -- it evaluates every RHS against
166
- // `ctx` in the loop, only ever writing into `child_ctx`.
158
+ // Each RHS is evaluated against the GROWING childCtx, so a later
159
+ // binding sees an earlier one -- `let(a=1, b=a+1) ...` binds b to 2.
160
+ // The statement form behaves exactly like the expression form here.
161
+ //
162
+ // This used to evaluate every RHS against the original `ctx` instead,
163
+ // described in a comment as a deliberate port of the reference's
164
+ // _eval_let_block. That reference was this project's own earlier
165
+ // implementation, which had the same bug; real OpenSCAD 2026.02.01
166
+ // echoes 2, and BOSL2 depends on it heavily -- isosurface.scad chains
167
+ // five bindings where each uses the previous, and rounding.scad's
168
+ // join_prism examples do the same.
167
169
  for (const auto& assign : node.assignments) {
168
- // Checked against the ORIGINAL ctx (like the RHS eval below), not
169
- // childCtx -- mirrors _eval_let_block's `_check_debug(assign, ctx)`.
170
- checkDebug(*assign, ctx);
171
- Value v = evalExprMaybeCompiled(*assign->expr, ctx);
170
+ checkDebug(*assign, childCtx);
171
+ Value v = evalExprMaybeCompiled(*assign->expr, childCtx);
172
172
  const std::string& name = assign->name->name;
173
173
  if (!name.empty() && name[0] == '$') {
174
174
  childCtx.dyn->set(name, v);
@@ -1889,15 +1889,21 @@ TEST(ModuleBodyCompiles, AssertStatementCompiledFormFailsWithNamedMessage) {
1889
1889
  }
1890
1890
  }
1891
1891
 
1892
- TEST(ModuleBodyCompiles, LetBlockStatementDoesNotSeeItsOwnEarlierSiblingAssignment) {
1892
+ TEST(ModuleBodyCompiles, LetBlockStatementSeesItsOwnEarlierSiblingAssignment) {
1893
1893
  ScopedVm vm(true);
1894
- // The statement form's own documented divergence from let-EXPRESSION
1895
- // sequential visibility: b's RHS must see the OUTER a (1), not this
1896
- // same let-block's own a=2 -- b should be 2, not 3.
1894
+ // b's RHS sees THIS let-block's own a=2, not the outer a=1, so b is 3.
1895
+ // The statement form has the same sequential visibility the expression
1896
+ // form does, and the binding still does not escape the block.
1897
+ //
1898
+ // This test previously asserted "ECHO: 2, 2" -- b seeing the outer a --
1899
+ // and called that a documented divergence. It was not: real OpenSCAD
1900
+ // 2026.02.01 echoes "2, 3" then "1", which is what both engines now
1901
+ // produce. BOSL2 depends on the sequential form (isosurface.scad chains
1902
+ // five bindings where each uses the previous).
1897
1903
  EXPECT_EQ(runCapturingEcho("a = 1;\n"
1898
1904
  "let (a = 2, b = a + 1) { echo(a, b); }\n"
1899
1905
  "echo(a);\n"),
1900
- "ECHO: 2, 2\nECHO: 1");
1906
+ "ECHO: 2, 3\nECHO: 1");
1901
1907
  }
1902
1908
 
1903
1909
  TEST(ModuleBodyCompiles, LetBlockStatementIsolatesDollarVarOverrideFromLaterStatements) {
@@ -428,6 +428,59 @@ TEST(EscapingClosure, CapturelessLiteralStillWorksCompiled) {
428
428
 
429
429
  // -- let (statement + expression forms) --------------------------------
430
430
 
431
+ // The let STATEMENT form has the same sequential visibility as the
432
+ // expression form: a later binding sees an earlier one. Verified against
433
+ // real OpenSCAD 2026.02.01, which echoes 2 for both forms below.
434
+ //
435
+ // This was wrong for a long time, described in-source as a deliberate port
436
+ // of "the reference's _eval_let_block" -- that reference being this
437
+ // project's own earlier implementation, which had the same bug. BOSL2 leans
438
+ // on it: isosurface.scad chains five bindings where each uses the previous,
439
+ // and rounding.scad's join_prism examples do too.
440
+
441
+ namespace {
442
+
443
+ void checkLetStatementIsSequential(bool useVm) {
444
+ ScopedVm vm(useVm);
445
+ std::vector<std::string> echoed;
446
+ runScript("let (a = 1, b = a + 1) echo(b = b);\n",
447
+ [&](const std::string& msg) { echoed.push_back(msg); });
448
+ EXPECT_EQ(echoed, (std::vector<std::string>{"ECHO: b = 2"}));
449
+ }
450
+
451
+ void checkLetStatementShadowsWithoutEscaping(bool useVm) {
452
+ ScopedVm vm(useVm);
453
+ std::vector<std::string> echoed;
454
+ // The inner a=2 is what b sees, and it does not survive the block.
455
+ runScript("a = 1;\n"
456
+ "let (a = 2, b = a + 1) echo(inside_a = a, inside_b = b);\n"
457
+ "echo(after_a = a);\n",
458
+ [&](const std::string& msg) { echoed.push_back(msg); });
459
+ EXPECT_EQ(echoed, (std::vector<std::string>{"ECHO: inside_a = 2, inside_b = 3",
460
+ "ECHO: after_a = 1"}));
461
+ }
462
+
463
+ void checkLetStatementChainsSeveralBindings(bool useVm) {
464
+ ScopedVm vm(useVm);
465
+ std::vector<std::string> echoed;
466
+ // The isosurface.scad shape: each binding built from the previous.
467
+ runScript("let (a = 2, b = a * 3, c = b + a, d = c * b) echo(d = d);\n",
468
+ [&](const std::string& msg) { echoed.push_back(msg); });
469
+ EXPECT_EQ(echoed, (std::vector<std::string>{"ECHO: d = 48"}));
470
+ }
471
+
472
+ } // namespace
473
+
474
+ TEST(LetStatementScope, IsSequentialCompiled) { checkLetStatementIsSequential(true); }
475
+ TEST(LetStatementScope, IsSequentialInterpreted) { checkLetStatementIsSequential(false); }
476
+
477
+ TEST(LetStatementScope, ShadowsWithoutEscapingCompiled) { checkLetStatementShadowsWithoutEscaping(true); }
478
+ TEST(LetStatementScope, ShadowsWithoutEscapingInterpreted) { checkLetStatementShadowsWithoutEscaping(false); }
479
+
480
+ TEST(LetStatementScope, ChainsSeveralBindingsCompiled) { checkLetStatementChainsSeveralBindings(true); }
481
+ TEST(LetStatementScope, ChainsSeveralBindingsInterpreted) { checkLetStatementChainsSeveralBindings(false); }
482
+
483
+
431
484
  TEST(LetStatement, BindsVisibleInsideBlockOnly) {
432
485
  Evaluated e = evalSrc("let(s=3) cube(s);");
433
486
  manifold::Box bbox = e.bodies[0].body->BoundingBox();
@@ -314,3 +314,81 @@ TEST(WarningTrace, FromClauseNamesOutermostFrameNotImmediateCaller) {
314
314
  EXPECT_NE(warning.find("from <string>, line 3"), std::string::npos) << warning;
315
315
  EXPECT_EQ(warning.find("from <string>, line 2"), std::string::npos) << warning;
316
316
  }
317
+
318
+ // -- hull() over an open mesh ------------------------------------------
319
+ //
320
+ // A convex hull needs only its input's POINTS; whether the mesh that
321
+ // carried them is closed is irrelevant. BOSL2 leans on this hard:
322
+ // hull_points(pts, fast=true) hands polyhedron() a deliberately garbage
323
+ // face list purely as a vehicle for the vertices, and
324
+ // spheroid(circum=true, style="icosa") goes through that path. Treating
325
+ // the open mesh as display-only left the loose triangles to fall out of
326
+ // hull() un-hulled -- the model rendered as a scatter of shards where the
327
+ // reference produced a solid.
328
+
329
+ TEST(HullOpenMesh, HullsThePointsOfAMeshThatDoesNotClose) {
330
+ Evaluated e = evalSrc(
331
+ "pts = [[0,0,0],[10,0,0],[10,10,0],[0,10,0],\n"
332
+ " [0,0,10],[10,0,10],[10,10,10],[0,10,10]];\n"
333
+ "hull() polyhedron(points=pts, faces=[[0,1,2],[3,4,5],[6,7,0]]);\n");
334
+
335
+ ASSERT_EQ(e.bodies.size(), 1u) << "the open mesh must be consumed, not passed through";
336
+ ASSERT_TRUE(e.bodies[0].body.has_value());
337
+ EXPECT_FALSE(e.bodies[0].isDisplayOnly());
338
+ EXPECT_EQ(e.bodies[0].body->Status(), manifold::Manifold::Error::NoError);
339
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 1000.0, 1e-6);
340
+ }
341
+
342
+ TEST(HullOpenMesh, AnOpenMeshAndASolidHullTogether) {
343
+ // The solid's own vertices must survive the switch to the point-based
344
+ // hull: hulling only the loose points would silently shrink the result.
345
+ Evaluated e = evalSrc(
346
+ "hull() {\n"
347
+ " polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0]], faces=[[0,1,2]]);\n"
348
+ " translate([0,0,10]) cube(10);\n"
349
+ "}\n");
350
+
351
+ ASSERT_EQ(e.bodies.size(), 1u);
352
+ ASSERT_TRUE(e.bodies[0].body.has_value());
353
+ EXPECT_EQ(e.bodies[0].body->Status(), manifold::Manifold::Error::NoError);
354
+ // Hull of the z=0 square and the 10..20 cube: a prism, strictly larger
355
+ // than the cube alone.
356
+ EXPECT_GT(e.bodies[0].body->Volume(), 1000.0);
357
+ }
358
+
359
+ TEST(HullOpenMesh, AnOpenMeshOutsideAHullStillPassesThroughUntouched) {
360
+ Evaluated e = evalSrc(
361
+ "polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0]], faces=[[0,1,2]]);\n");
362
+
363
+ ASSERT_EQ(e.bodies.size(), 1u);
364
+ EXPECT_TRUE(e.bodies[0].isDisplayOnly()) << "only hull() may consume it";
365
+ }
366
+
367
+ TEST(HullOpenMesh, NoWarningForAnOpenMeshInsideAHull) {
368
+ // The reference is silent here, and BOSL2 reaches this on an everyday
369
+ // spheroid(circum=true, style="icosa") -- warning would cry wolf.
370
+ std::string warning;
371
+ evalSrc("hull() polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0],\n"
372
+ " [0,0,10],[10,0,10],[10,10,10],[0,10,10]],\n"
373
+ " faces=[[0,1,2],[3,4,5],[6,7,0]]);\n",
374
+ [&](const std::string& m) { if (warning.empty()) warning = m; });
375
+ EXPECT_EQ(warning, "") << warning;
376
+ }
377
+
378
+ TEST(HullOpenMesh, SuppressionCoversTheWholeSubtreeNotJustDirectChildren) {
379
+ std::string warning;
380
+ evalSrc("hull() { translate([0,0,1]) polyhedron(\n"
381
+ " points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0]], faces=[[0,1,2]]); cube(1); }\n",
382
+ [&](const std::string& m) { if (warning.empty()) warning = m; });
383
+ EXPECT_EQ(warning, "") << warning;
384
+ }
385
+
386
+ TEST(HullOpenMesh, ASiblingAfterAHullStillWarns) {
387
+ // The flag is restored on the way out; an open mesh that is NOT hulled
388
+ // is still a real mistake.
389
+ std::string warning;
390
+ evalSrc("hull() cube(1);\n"
391
+ "polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0]], faces=[[0,1,2]]);\n",
392
+ [&](const std::string& m) { if (warning.empty()) warning = m; });
393
+ EXPECT_NE(warning.find("mesh is not closed"), std::string::npos) << warning;
394
+ }
@@ -211,6 +211,32 @@ TEST(Polyhedron, OpenMeshIsKeptForDisplayAndWarns) {
211
211
  // The originalID is what maps a picked triangle back to its source line, so
212
212
  // a display-only body has to carry one or clicking it would select nothing
213
213
  // -- precisely when a user most wants to be shown the offending code.
214
+ // A polyhedron with points but NO faces is empty geometry, not a failure.
215
+ // BOSL2's debug_vnf() passes [verts, []] to draw vertex labels with no
216
+ // surface at all, and real OpenSCAD 2026.02.01 accepts
217
+ // `polyhedron(points=..., faces=[])` silently. This used to report the
218
+ // empty mesh as NotManifold and discard it with a warning, which failed
219
+ // four BOSL2 geometry.scad examples.
220
+ TEST(Polyhedron, NoFacesIsEmptyGeometryAndSilent) {
221
+ std::string warning;
222
+ Evaluated e = evalSrc("polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0]], faces=[]);",
223
+ [&](const std::string& m) { warning = m; });
224
+
225
+ ASSERT_EQ(e.bodies.size(), 1u);
226
+ EXPECT_TRUE(e.bodies[0].body->IsEmpty());
227
+ EXPECT_FALSE(e.bodies[0].isDisplayOnly()); // nothing to display, not an open surface
228
+ EXPECT_EQ(warning, "") << warning;
229
+ }
230
+
231
+ TEST(Polyhedron, NoPointsAndNoFacesIsAlsoSilent) {
232
+ std::string warning;
233
+ Evaluated e = evalSrc("polyhedron(points=[], faces=[]);",
234
+ [&](const std::string& m) { warning = m; });
235
+ ASSERT_EQ(e.bodies.size(), 1u);
236
+ EXPECT_TRUE(e.bodies[0].body->IsEmpty());
237
+ EXPECT_EQ(warning, "") << warning;
238
+ }
239
+
214
240
  TEST(Polyhedron, OpenMeshCarriesAnOriginalIdForPicking) {
215
241
  Evaluated e = evalSrc(kOpenCube);
216
242
  ASSERT_EQ(e.bodies.size(), 1u);