openscad-cpp-evaluator 0.55.2__tar.gz → 0.55.4__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.55.2 → openscad_cpp_evaluator-0.55.4}/CLAUDE.md +9 -0
  2. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/bytecode_vm.hpp +3 -0
  4. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/colored_body.hpp +16 -1
  5. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/python/openscad_cpp_evaluator/__init__.py +2 -1
  7. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/booleans.cpp +47 -0
  8. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/transforms.cpp +4 -0
  9. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/bytecode_compiler.cpp +15 -1
  10. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/bytecode_vm.cpp +6 -1
  11. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/colored_body.cpp +11 -1
  12. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/export.cpp +1 -1
  13. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_booleans.cpp +118 -10
  14. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_control_flow.cpp +6 -6
  15. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_export.cpp +4 -2
  16. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/.github/workflows/ci.yml +0 -0
  17. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/.github/workflows/wheels.yml +0 -0
  18. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/.gitignore +0 -0
  19. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/.gitmodules +0 -0
  20. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/CMakeLists.txt +0 -0
  21. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/LICENSE +0 -0
  22. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/README.md +0 -0
  23. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/bindings/CMakeLists.txt +0 -0
  24. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/bindings/ast_to_py.cpp +0 -0
  25. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/bindings/module.cpp +0 -0
  26. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/cmake/embed_font.cmake +0 -0
  27. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/examples/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/examples/eval_perf_benchmark.cpp +0 -0
  29. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/examples/manifold_cache_reuse.cpp +0 -0
  30. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/examples/minimal_debugger.cpp +0 -0
  31. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  32. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/.gitignore +0 -0
  33. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  34. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  35. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/README.md +0 -0
  36. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  37. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  38. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  39. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  40. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  41. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  42. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  43. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  44. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  45. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  46. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  47. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  48. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  49. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  50. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  51. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  52. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/api.cpp +0 -0
  53. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  54. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  55. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  56. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  57. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  58. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  59. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  60. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  61. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  62. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  63. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  64. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  65. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  66. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  67. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  68. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  69. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/position.cpp +0 -0
  70. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  71. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  72. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  73. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  74. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  75. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  76. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  77. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  78. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  79. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  80. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  81. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  82. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  83. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  84. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  85. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  86. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  87. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  88. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  89. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  90. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  91. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  92. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  93. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  94. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  95. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  96. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  97. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  98. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  99. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  100. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  101. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  102. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  103. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  104. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  105. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  106. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  107. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  108. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  109. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  110. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  111. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  112. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/export.hpp +0 -0
  113. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  114. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  116. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  117. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  118. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  119. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  120. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  121. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  122. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  123. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  124. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  125. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  126. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  127. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/value.hpp +0 -0
  128. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  129. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/resources/fonts/LICENSE +0 -0
  130. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  131. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/CMakeLists.txt +0 -0
  132. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/builtins.hpp +0 -0
  133. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/call_args.cpp +0 -0
  134. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/color.cpp +0 -0
  135. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/control.cpp +0 -0
  136. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/dxf_dim.cpp +0 -0
  137. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/extrude.cpp +0 -0
  138. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/function_builtins.cpp +0 -0
  139. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/import.cpp +0 -0
  140. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/modifiers.cpp +0 -0
  141. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/primitives_2d.cpp +0 -0
  142. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/primitives_3d.cpp +0 -0
  143. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/registry.cpp +0 -0
  144. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/roof.cpp +0 -0
  145. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/segments.cpp +0 -0
  146. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/surface.cpp +0 -0
  147. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/text.cpp +0 -0
  148. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/builtins/topology.cpp +0 -0
  149. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/csg_generate.cpp +0 -0
  150. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/csg_resolve.cpp +0 -0
  151. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/css_colors.cpp +0 -0
  152. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/debug/debug_repl.cpp +0 -0
  153. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/debug_profile.cpp +0 -0
  154. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/eval_context.cpp +0 -0
  155. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/eval_error.cpp +0 -0
  156. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/eval_use.cpp +0 -0
  157. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/expr_eval.cpp +0 -0
  158. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/import/dxf_import.cpp +0 -0
  159. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/import/mesh_import.cpp +0 -0
  160. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/import/surface_load.cpp +0 -0
  161. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/import/svg_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/manifold_cache.cpp +0 -0
  163. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/measure_geometry.cpp +0 -0
  164. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/mesh_check.cpp +0 -0
  165. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/stmt_eval.cpp +0 -0
  166. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/text/font_match.cpp +0 -0
  167. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/text/freetype_font_provider.cpp +0 -0
  168. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/text/text_metrics.cpp +0 -0
  169. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/user_calls.cpp +0 -0
  170. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/value.cpp +0 -0
  171. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/src/zip_stored.cpp +0 -0
  172. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/CMakeLists.txt +0 -0
  173. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_bytecode_compiler.cpp +0 -0
  174. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_children_separate.cpp +0 -0
  175. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_cli.cpp +0 -0
  176. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_csg_tree.cpp +0 -0
  177. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_debug_hooks.cpp +0 -0
  178. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_dxf_svg_import.cpp +0 -0
  179. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_eval_context.cpp +0 -0
  180. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_expr_eval.cpp +0 -0
  181. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_extrude_roof.cpp +0 -0
  182. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_feature_detection.cpp +0 -0
  183. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_function_builtins.cpp +0 -0
  184. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_helpers.hpp +0 -0
  185. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_import_export.cpp +0 -0
  186. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_manifold_cache.cpp +0 -0
  187. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_mesh_check.cpp +0 -0
  188. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_multi_color_merge.cpp +0 -0
  189. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_primitives.cpp +0 -0
  190. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_profiling.cpp +0 -0
  191. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_python_bindings.py +0 -0
  192. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_render_expr.cpp +0 -0
  193. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_scope_trail.cpp +0 -0
  194. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_scoping.cpp +0 -0
  195. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_surface.cpp +0 -0
  196. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_tail_calls.cpp +0 -0
  197. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_text.cpp +0 -0
  198. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_transforms.cpp +0 -0
  199. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_unexpected_args.cpp +0 -0
  200. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_use.cpp +0 -0
  201. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_value.cpp +0 -0
  202. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_viewport_params.cpp +0 -0
  203. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tests/test_zip_stored.cpp +0 -0
  204. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tools/cli/CMakeLists.txt +0 -0
  205. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tools/cli/cli_lib.cpp +0 -0
  206. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tools/cli/cli_lib.hpp +0 -0
  207. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/tools/cli/main.cpp +0 -0
  208. {openscad_cpp_evaluator-0.55.2 → openscad_cpp_evaluator-0.55.4}/uv.lock +0 -0
@@ -231,6 +231,15 @@ top-level 2D primitive/projection() script had been silently unexportable via th
231
231
  Fixed by porting `toRenderableBodies()` (`colored_body.hpp`/`.cpp`) and calling it from the CLI right
232
232
  before export, matching the reference's own call site exactly.
233
233
 
234
+ `ColoredBody::sectionZ` rides alongside for the same reason. A `manifold::CrossSection` is purely
235
+ 2D, so `down(1) square(10)` had nowhere to put the `-1` and silently dropped it — and that is not
236
+ cosmetic: BOSL2 layers 2D overlays by pushing one down (isosurface's `contour()` Example 1 puts a
237
+ blue backdrop below a green contour), and with the offset lost the two slabs became exactly
238
+ coplanar, z-fought, and whichever drew last covered the other completely. `generateTransform`
239
+ accumulates the z of any `translate` applied to a section, and `toRenderableBodies()` translates the
240
+ extruded slab by it. Only translation is carried — a 2D shape under a 3D *rotation* still loses it,
241
+ which would need a full matrix per section and nothing has asked for that yet.
242
+
234
243
  **Font support**: a `FontProvider` abstract interface (`font_provider.hpp`: `resolveFont`/
235
244
  `metrics`/`shapeText`/`glyphInkBounds`/`glyphOutline`) with `FreetypeFontProvider` as the built-in
236
245
  implementation everything here actually uses. `Evaluator`'s constructor takes an optional
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.55.2
3
+ Version: 0.55.4
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -68,6 +68,9 @@ struct PendingCsgWrap {
68
68
  std::uint64_t randsBefore = 0;
69
69
  int siteIdx = -1;
70
70
  std::vector<Value> groupSizes;
71
+ // Parallel to groupSizes: whether a group that came out EMPTY is still
72
+ // an operand. See Op::CsgGroupEnd's `a`.
73
+ std::vector<Value> emptyIsAGroup;
71
74
  size_t groupStartSize = 0;
72
75
  };
73
76
 
@@ -24,6 +24,21 @@ struct ColoredBody {
24
24
  std::optional<std::array<float, 4>> color; // RGBA; nullopt = "no explicit color() -- follow the live theme"
25
25
  std::optional<manifold::CrossSection> section;
26
26
  bool flatPreview = false;
27
+
28
+ // Where a 2D `section` sits along Z. Only meaningful while `section` is
29
+ // set, and applied when toRenderableBodies() extrudes it.
30
+ //
31
+ // A manifold::CrossSection is purely 2D, so `down(1) square(10)` had
32
+ // nowhere to put the -1 and silently dropped it. That is not merely
33
+ // cosmetic: BOSL2 layers 2D overlays by pushing one down (isosurface's
34
+ // contour() Example 1 puts a blue backdrop below a green contour), and
35
+ // with the offset lost the two slabs became exactly coplanar, z-fought,
36
+ // and whichever was drawn last covered the other completely.
37
+ //
38
+ // Only translation is carried. A 2D shape under a 3D ROTATION still
39
+ // loses it -- representing that needs a full matrix per section, which
40
+ // nothing has asked for yet.
41
+ double sectionZ = 0.0;
27
42
  BodyRole role = BodyRole::Normal;
28
43
  std::optional<std::vector<std::array<float, 4>>> triColors; // per-triangle RGBA, multi-color CSG merges only
29
44
 
@@ -60,7 +75,7 @@ Value colorToValue(const std::optional<std::array<double, 4>>& color);
60
75
  std::optional<std::array<float, 4>> valueToColor(const Value& v);
61
76
 
62
77
  // Converts every top-level 2D-only result (`body` unset, `section` set --
63
- // e.g. a bare `circle();`) into a thin-extruded (1e-3 units tall) Manifold
78
+ // e.g. a bare `circle();`) into a 1-unit-tall extruded Manifold
64
79
  // tagged `flatPreview = true`, so a mesh exporter/renderer that only
65
80
  // understands 3D Manifolds (writeStl/writeObj/writeOff/writeThreeMf) can
66
81
  // still show a 2D top-level result instead of silently dropping it. 3D
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.55.2"
7
+ version = "0.55.4"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -213,7 +213,8 @@ def _summarize_param(value, max_items: int = 6, max_len: int = 40) -> str:
213
213
  # Params keys never shown in format_csg_tree's per-node summary, regardless
214
214
  # of kind -- pure internal bookkeeping or already represented structurally
215
215
  # elsewhere in the tree. Mirrors the reference's _DUMP_HIDDEN_PARAM_KEYS.
216
- _DUMP_HIDDEN_PARAM_KEYS = frozenset({"color", "op", "name", "group_sizes"})
216
+ _DUMP_HIDDEN_PARAM_KEYS = frozenset(
217
+ {"color", "op", "name", "group_sizes", "empty_is_a_group"})
217
218
 
218
219
  # Auto-generated tessellation data (not user-authored) for every kind except
219
220
  # polyhedron, where the equivalent data *is* the user's own points/faces.
@@ -108,16 +108,43 @@ CSGParams resolveCsg(Evaluator& ev, const oscad::ModularCall& node, EvalContext&
108
108
  if (!assignNodes.empty()) ev.evalChildren(assignNodes, blockCtx);
109
109
 
110
110
  std::vector<Value> groupSizes;
111
+ std::vector<Value> emptyIsAGroup;
111
112
  groupSizes.reserve(geoNodes.size());
113
+ emptyIsAGroup.reserve(geoNodes.size());
112
114
  for (const oscad::ASTNode* geoNode : geoNodes) {
113
115
  const size_t before = ev.currentTreeFrameSize();
114
116
  ev.evalChildren(std::vector<const oscad::ASTNode*>{geoNode}, blockCtx);
115
117
  groupSizes.push_back(Value{static_cast<double>(ev.currentTreeFrameSize() - before)});
118
+ // Whether a statement that produced NOTHING still counts as an
119
+ // operand.
120
+ //
121
+ // The reference builds a node for a module instantiation and for a
122
+ // loop whatever they contain, so an empty one is an empty operand
123
+ // and annihilates an intersection. An `if` that takes no branch, a
124
+ // bare block, and a `*`-disabled statement build no node at all, so
125
+ // they are not operands and must be skipped.
126
+ //
127
+ // Every line of this was checked against OpenSCAD rather than
128
+ // reasoned about -- `intersection(){ cube(10); X; }` keeps the cube
129
+ // for `if(false) sphere(6)`, `{ }` and `*cube(1)`, and comes out
130
+ // empty for `for(i=[1:0]) sphere(6)`, `union(){}`, `group(){}` and
131
+ // a call to an empty user module.
132
+ const oscad::NodeKind k = geoNode->kind();
133
+ emptyIsAGroup.push_back(Value{
134
+ k == oscad::NodeKind::ModularCall ||
135
+ k == oscad::NodeKind::ModularFor ||
136
+ k == oscad::NodeKind::ModularIntersectionFor ||
137
+ k == oscad::NodeKind::ModularLet ||
138
+ k == oscad::NodeKind::ModularModifierShowOnly ||
139
+ k == oscad::NodeKind::ModularModifierHighlight ||
140
+ k == oscad::NodeKind::ModularModifierBackground});
116
141
  }
117
142
 
118
143
  CSGParams params;
119
144
  params["op"] = Value{op};
120
145
  params["group_sizes"] = Value{std::make_shared<const ValueList>(ValueList{std::move(groupSizes)})};
146
+ params["empty_is_a_group"] =
147
+ Value{std::make_shared<const ValueList>(ValueList{std::move(emptyIsAGroup)})};
121
148
  return params;
122
149
  }
123
150
 
@@ -180,16 +207,36 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params, con
180
207
  const oscad::ASTNode&) {
181
208
  const std::string& op = std::get<std::string>(params.at("op"));
182
209
  const auto& groupSizes = std::get<ListPtr>(params.at("group_sizes"))->items;
210
+ const auto emptyIsAGroupIt = params.find("empty_is_a_group");
211
+ const std::vector<Value>* emptyIsAGroup =
212
+ emptyIsAGroupIt == params.end()
213
+ ? nullptr
214
+ : &std::get<ListPtr>(emptyIsAGroupIt->second)->items;
183
215
 
184
216
  std::vector<ColoredBody> allBg, allHi, allSo, allDo;
185
217
  std::optional<ColoredBody> csgResult;
186
218
  size_t idx = 0;
187
219
 
220
+ size_t stmtIndex = 0;
188
221
  for (const Value& sizeVal : groupSizes) {
189
222
  const size_t size = static_cast<size_t>(std::get<double>(sizeVal));
223
+ const size_t thisStmt = stmtIndex++;
190
224
  std::vector<ColoredBody> stmtBodies = flattenCsgTree(children, idx, size);
191
225
  idx += size;
192
226
 
227
+ // A statement that built no CSG node at all is not an operand --
228
+ // an `if` whose branch was not taken, or an empty block. Only a
229
+ // for-loop still counts as one when it produced nothing, matching
230
+ // the group node the reference builds for it regardless. Without
231
+ // this, `intersection(){ X; if (crop) Y; }` -- BOSL2's hirth()
232
+ // among others -- came out empty whenever the condition was false.
233
+ if (size == 0) {
234
+ const bool isGroup = emptyIsAGroup && thisStmt < emptyIsAGroup->size() &&
235
+ std::holds_alternative<bool>((*emptyIsAGroup)[thisStmt]) &&
236
+ std::get<bool>((*emptyIsAGroup)[thisStmt]);
237
+ if (!isGroup) continue;
238
+ }
239
+
193
240
  RoleSplit split = splitByRole(stmtBodies);
194
241
  allBg.insert(allBg.end(), split.background.begin(), split.background.end());
195
242
  allHi.insert(allHi.end(), split.highlight.begin(), split.highlight.end());
@@ -233,6 +233,10 @@ std::vector<ColoredBody> generateTransform(Evaluator&, const CSGParams& params,
233
233
  std::vector<ColoredBody> result;
234
234
  for (ColoredBody b : flattenCsgTree(children)) {
235
235
  if (b.section) {
236
+ // The CrossSection takes x/y; z has nowhere to go in it, so it
237
+ // rides along on the body until the section is extruded. See
238
+ // ColoredBody::sectionZ.
239
+ if (name == "translate") b.sectionZ += toVec3(getArg(args, 0, "v", Value{})).z;
236
240
  b.section = applyTransform2d(name, args, std::move(*b.section));
237
241
  } else if (b.body) {
238
242
  b.body = applyTransform3d(name, args, std::move(*b.body));
@@ -1348,7 +1348,21 @@ public:
1348
1348
  }
1349
1349
  out.push_back({Op::CsgGroupStart, 0, 0, nullptr});
1350
1350
  compileStatementList(std::vector<const oscad::ASTNode*>{geoNode}, out);
1351
- out.push_back({Op::CsgGroupEnd, 0, 0, nullptr});
1351
+ // `a` = whether a group that turns out EMPTY is still an
1352
+ // operand: a module instantiation or a loop builds a node
1353
+ // whatever it contains, an `if`/block/`*` does not. Mirrors
1354
+ // resolveCsg's own "empty_is_a_group" (booleans.cpp), where the
1355
+ // full reasoning and the OpenSCAD checks live.
1356
+ const oscad::NodeKind k = geoNode->kind();
1357
+ const int emptyIsAGroup = (
1358
+ k == oscad::NodeKind::ModularCall ||
1359
+ k == oscad::NodeKind::ModularFor ||
1360
+ k == oscad::NodeKind::ModularIntersectionFor ||
1361
+ k == oscad::NodeKind::ModularLet ||
1362
+ k == oscad::NodeKind::ModularModifierShowOnly ||
1363
+ k == oscad::NodeKind::ModularModifierHighlight ||
1364
+ k == oscad::NodeKind::ModularModifierBackground) ? 1 : 0;
1365
+ out.push_back({Op::CsgGroupEnd, emptyIsAGroup, 0, nullptr});
1352
1366
  }
1353
1367
  out.push_back({Op::CloseExprScope, 0, 0, nullptr});
1354
1368
  out.push_back({Op::PopCsgWrap, idx, 0, &call.position()});
@@ -1311,7 +1311,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1311
1311
  // ITERATION child ctxs are pushed separately, by the
1312
1312
  // compiled loop's own Op::ForIterNext.
1313
1313
  ev.treeStack_.emplace_back();
1314
- f.csgWrapStack.push_back({site.op, randsBefore, ins.a, {}, 0});
1314
+ f.csgWrapStack.push_back({site.op, randsBefore, ins.a, {}, {}, 0});
1315
1315
  ++f.pc;
1316
1316
  break;
1317
1317
  }
@@ -1336,6 +1336,8 @@ Value driveVm(Evaluator& ev, size_t floor) {
1336
1336
  ev.evalChildren(std::vector<const oscad::ASTNode*>{stmt}, ctx);
1337
1337
  pending.groupSizes.push_back(
1338
1338
  Value{static_cast<double>(ev.treeStack_.back().size() - before)});
1339
+ // A spliced child is a plain statement, never a loop.
1340
+ pending.emptyIsAGroup.push_back(Value{false});
1339
1341
  }
1340
1342
  ++f.pc;
1341
1343
  break;
@@ -1344,6 +1346,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1344
1346
  PendingCsgWrap& pending = f.csgWrapStack.back();
1345
1347
  const size_t after = ev.treeStack_.back().size();
1346
1348
  pending.groupSizes.push_back(Value{static_cast<double>(after - pending.groupStartSize)});
1349
+ pending.emptyIsAGroup.push_back(Value{ins.a != 0});
1347
1350
  ++f.pc;
1348
1351
  break;
1349
1352
  }
@@ -1367,6 +1370,8 @@ Value driveVm(Evaluator& ev, size_t floor) {
1367
1370
  if (site.includeOpParam) params["op"] = Value{pending.op};
1368
1371
  params["group_sizes"] =
1369
1372
  Value{std::make_shared<const ValueList>(ValueList{std::move(pending.groupSizes)})};
1373
+ params["empty_is_a_group"] = Value{std::make_shared<const ValueList>(
1374
+ ValueList{std::move(pending.emptyIsAGroup)})};
1370
1375
  // Mirrors Evaluator::buildTreeNode's own post-
1371
1376
  // resolveBody() half exactly (csg_resolve.cpp).
1372
1377
  const bool uncacheable =
@@ -21,7 +21,13 @@ std::optional<std::array<float, 4>> valueToColor(const Value& v) {
21
21
  }
22
22
 
23
23
  namespace {
24
- constexpr double kTopLevel2dHeight = 1e-3;
24
+ // The reference extrudes a 2D shape to exactly 1 unit for preview -- measured
25
+ // against OpenSCAD at three camera tilts (60/70/80 degrees), which put it at
26
+ // 0.92/1.00/0.99, the 60 being pixel-rounding on the shallowest angle. Ours
27
+ // was 1e-3, which reads as a flat silhouette with no visible edge and, more
28
+ // annoyingly, gives --viewall a different bounding box to fit, so every 2D
29
+ // docs image came out at a different scale from the published one.
30
+ constexpr double kTopLevel2dHeight = 1.0;
25
31
  } // namespace
26
32
 
27
33
  std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodies) {
@@ -31,6 +37,10 @@ std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodi
31
37
  if (!cb.body && cb.section) {
32
38
  ColoredBody flat;
33
39
  flat.body = manifold::Manifold::Extrude(cb.section->ToPolygons(), kTopLevel2dHeight);
40
+ // Put it back where a 3D translate moved it -- the CrossSection
41
+ // could not hold that. See ColoredBody::sectionZ.
42
+ if (cb.sectionZ != 0.0)
43
+ flat.body = flat.body->Translate(manifold::vec3(0, 0, cb.sectionZ));
34
44
  flat.color = cb.color;
35
45
  flat.flatPreview = true;
36
46
  flat.role = cb.role;
@@ -432,7 +432,7 @@ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& b
432
432
  // A `flatPreview` body is a top-level 2D shape thin-extruded so it can be
433
433
  // SEEN (toRenderableBodies). The top level keeps 2D and 3D side by side
434
434
  // for exactly that reason -- but a mesh export of a mixed script must
435
- // not smuggle a 1e-3-tall slab in beside the real solids, which is what
435
+ // not smuggle a 1-unit-tall slab in beside the real solids, which is what
436
436
  // the reference drops there too. A 2D-ONLY script still exports its
437
437
  // slab, unchanged: that is the only geometry there is, and dropping it
438
438
  // would leave nothing to write.
@@ -40,11 +40,76 @@ TEST(Intersection, KeepsOnlyOverlappingVolume) {
40
40
  EXPECT_NEAR(e.bodies[0].body->Volume(), 1.0, 1e-6);
41
41
  }
42
42
 
43
- TEST(Intersection, DisabledStatementIsTrulyEmptyAndDiscardsResult) {
43
+ TEST(Intersection, ADisabledStatementIsNotAnOperandAtAll) {
44
+ // `*cube(1)` produces no CSGNode, so it is not an operand -- the
45
+ // intersection has one child and returns it. This used to assert an
46
+ // EMPTY result on the reasoning that a statement contributing no bodies
47
+ // is "intersection with nothing"; checking against OpenSCAD showed that
48
+ // is wrong (it gives volume 8, the cube(2)), and the same mistake is
49
+ // what made BOSL2's hirth() render nothing at all: its geometry is
50
+ // `intersection(){ ...; if (crop) tube(...); }` and crop defaults false.
44
51
  Evaluated e = evalSrc("intersection() { cube(2); *cube(1); }");
45
- // *cube(1) produces no CSGNode at all -> its "statement" contributes a
46
- // group of size 0 -- flattening an empty group yields no bodies, which
47
- // intersection() treats as "intersection with nothing" -> empty result.
52
+ ASSERT_EQ(e.bodies.size(), 1u);
53
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 8.0, 1e-6);
54
+ }
55
+
56
+ TEST(Intersection, AnUntakenIfIsNotAnOperandEither) {
57
+ // The reported case, reduced. Verified against OpenSCAD: volume 1000.
58
+ Evaluated e = evalSrc("intersection() { cube(10); if (false) sphere(6); }");
59
+ ASSERT_EQ(e.bodies.size(), 1u);
60
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 1000.0, 1e-6);
61
+ }
62
+
63
+ TEST(Intersection, EmptyStatementsFollowTheReferenceExactly) {
64
+ // Every row checked against OpenSCAD, not reasoned about. The rule that
65
+ // falls out: a module instantiation or a loop builds a node whatever it
66
+ // contains, so an empty one is an empty operand and annihilates; an
67
+ // `if` with no branch taken, a bare block and a `*`-disabled statement
68
+ // build no node at all and are not operands.
69
+ struct Case { const char* stmt; bool keepsTheCube; };
70
+ const Case cases[] = {
71
+ {"if (false) sphere(6);", true},
72
+ {"{ }", true},
73
+ {"*cube(1);", true},
74
+ {"for (i = [1:0]) sphere(6);", false},
75
+ {"union() { }", false},
76
+ {"group() { }", false},
77
+ {"let(x = 1) { }", false},
78
+ {"#union() { }", false},
79
+ };
80
+ for (const Case& c : cases) {
81
+ const std::string src =
82
+ std::string("intersection() { cube(10, center=true); ") + c.stmt + " }";
83
+ Evaluated e = evalSrc(src);
84
+ if (c.keepsTheCube) {
85
+ ASSERT_EQ(e.bodies.size(), 1u) << src;
86
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 1000.0, 1e-6) << src;
87
+ } else {
88
+ const bool anyVolume = std::any_of(
89
+ e.bodies.begin(), e.bodies.end(),
90
+ [](const ColoredBody& b) { return b.body && !b.body->IsEmpty(); });
91
+ EXPECT_FALSE(anyVolume) << src;
92
+ }
93
+ }
94
+ }
95
+
96
+ TEST(Intersection, AnEmptyUserModuleIsStillAnOperand) {
97
+ // The reference builds a node for a module instantiation whatever the
98
+ // module's body does, so calling one that draws nothing empties the
99
+ // intersection. Verified against OpenSCAD (writes no STL).
100
+ Evaluated e = evalSrc("module m() { } intersection() { cube(10); m(); }");
101
+ const bool anyVolume = std::any_of(
102
+ e.bodies.begin(), e.bodies.end(),
103
+ [](const ColoredBody& b) { return b.body && !b.body->IsEmpty(); });
104
+ EXPECT_FALSE(anyVolume);
105
+ }
106
+
107
+ TEST(Intersection, AnEmptyForLoopIsStillAnOperand) {
108
+ // The one empty statement that DOES annihilate: the reference builds a
109
+ // group node for a for-loop however many times it iterates, so an empty
110
+ // one is an empty operand. Verified against OpenSCAD, which writes no
111
+ // STL at all for this.
112
+ Evaluated e = evalSrc("intersection() { cube(10); for (i = [1:0]) sphere(6); }");
48
113
  EXPECT_TRUE(e.bodies.empty());
49
114
  }
50
115
 
@@ -73,14 +138,19 @@ TEST(Union, MultipleBodiesInFirstStatementAllSurvive) {
73
138
  EXPECT_NEAR(e.bodies[0].body->Volume(), 20.0, 1e-6);
74
139
  }
75
140
 
76
- TEST(Intersection, EmptyFirstOperandGivesEmpty) {
141
+ TEST(Intersection, ADisabledFirstStatementLetsTheNextOneLead) {
142
+ // Verified against OpenSCAD: volume 8, not empty.
77
143
  Evaluated e = evalSrc("intersection() { *cube(10); cube(2); }");
78
- EXPECT_TRUE(e.bodies.empty());
144
+ ASSERT_EQ(e.bodies.size(), 1u);
145
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 8.0, 1e-6);
79
146
  }
80
147
 
81
- TEST(Difference, EmptyFirstOperandGivesEmpty) {
148
+ TEST(Difference, ADisabledFirstStatementLetsTheNextOneBecomeTheBase) {
149
+ // Verified against OpenSCAD: volume 8 -- cube(2) becomes the base
150
+ // rather than the difference collapsing to nothing.
82
151
  Evaluated e = evalSrc("difference() { *cube(10); cube(2); }");
83
- EXPECT_TRUE(e.bodies.empty());
152
+ ASSERT_EQ(e.bodies.size(), 1u);
153
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 8.0, 1e-6);
84
154
  }
85
155
 
86
156
  TEST(Difference, EmptySubtractorLeavesBaseUnchanged) {
@@ -625,7 +695,7 @@ TEST(MixedDimensions, TopLevelWarnsButKeepsBothDimensions) {
625
695
 
626
696
  TEST(MixedDimensions, ExportDropsTheFlatSlabWhenRealSolidsArePresent) {
627
697
  // The top level keeps a 2D shape so it can be SEEN, thin-extruded by
628
- // toRenderableBodies. A mesh export must not smuggle that 1e-3-tall slab
698
+ // toRenderableBodies. A mesh export must not smuggle that 1-unit-tall slab
629
699
  // in beside the real solids -- the reference drops it there too.
630
700
  Evaluated e = evalSrc("square(4); cube(1);");
631
701
  const std::vector<ColoredBody> renderable = toRenderableBodies(e.bodies);
@@ -633,7 +703,7 @@ TEST(MixedDimensions, ExportDropsTheFlatSlabWhenRealSolidsArePresent) {
633
703
 
634
704
  const std::vector<ExportObject> objects = splitBodiesForExport(renderable, nullptr);
635
705
  ASSERT_EQ(objects.size(), 1u) << "but only the cube is exported";
636
- // The cube is a unit tall; the dropped slab would have been 1e-3.
706
+ // Only the cube survives, so the height is the cube's own.
637
707
  float zmin = 1e9f, zmax = -1e9f;
638
708
  for (size_t i = 2; i < objects[0].verts.size(); i += 3) {
639
709
  zmin = std::min(zmin, objects[0].verts[i]);
@@ -651,6 +721,44 @@ TEST(MixedDimensions, ExportKeepsTheFlatSlabWhenItIsTheOnlyGeometry) {
651
721
  EXPECT_EQ(objects.size(), 1u);
652
722
  }
653
723
 
724
+ TEST(MixedDimensions, ATranslatedTwoDeeShapeKeepsItsZ) {
725
+ // A CrossSection is purely 2D, so `translate([0,0,-1]) square(10)` had
726
+ // nowhere to put the -1 and dropped it. Two 2D shapes meant to be
727
+ // layered then came out exactly coplanar, z-fought, and whichever drew
728
+ // last hid the other -- BOSL2's isosurface contour() Example 1 renders a
729
+ // green contour over a blue backdrop pushed down by 1, and the backdrop
730
+ // covered the contour completely.
731
+ Evaluated e = evalSrc("translate([0,0,-1]) square(10, center=true);");
732
+ ASSERT_EQ(e.bodies.size(), 1u);
733
+ ASSERT_TRUE(e.bodies[0].section.has_value());
734
+ EXPECT_DOUBLE_EQ(e.bodies[0].sectionZ, -1.0);
735
+
736
+ const std::vector<ColoredBody> renderable = toRenderableBodies(e.bodies);
737
+ ASSERT_EQ(renderable.size(), 1u);
738
+ ASSERT_TRUE(renderable[0].body.has_value());
739
+ const manifold::Box box = renderable[0].body->BoundingBox();
740
+ EXPECT_NEAR(box.min.z, -1.0, 1e-9) << "the extruded slab sits where it was moved to";
741
+ }
742
+
743
+ TEST(MixedDimensions, TwoDeeZOffsetsAccumulateAndDefaultToZero) {
744
+ Evaluated flat = evalSrc("square(10);");
745
+ ASSERT_EQ(flat.bodies.size(), 1u);
746
+ EXPECT_DOUBLE_EQ(flat.bodies[0].sectionZ, 0.0);
747
+
748
+ Evaluated stacked = evalSrc(
749
+ "translate([0,0,2]) translate([0,0,3]) square(10);");
750
+ ASSERT_EQ(stacked.bodies.size(), 1u);
751
+ EXPECT_DOUBLE_EQ(stacked.bodies[0].sectionZ, 5.0);
752
+
753
+ // x/y still go into the CrossSection itself, not the offset.
754
+ Evaluated moved = evalSrc("translate([4,5,6]) square(10);");
755
+ ASSERT_EQ(moved.bodies.size(), 1u);
756
+ EXPECT_DOUBLE_EQ(moved.bodies[0].sectionZ, 6.0);
757
+ const manifold::Rect r = moved.bodies[0].section->Bounds();
758
+ EXPECT_NEAR(r.min.x, 4.0, 1e-9);
759
+ EXPECT_NEAR(r.min.y, 5.0, 1e-9);
760
+ }
761
+
654
762
  TEST(MixedDimensions, UniformChildrenAreUntouched) {
655
763
  EXPECT_TRUE(dimWarnings("union() { cube(1); cube(2); }").empty());
656
764
  EXPECT_TRUE(dimWarnings("union() { square(1); square(2); }").empty());
@@ -845,15 +845,15 @@ TEST(ExprEvalFunctionCall, CallingANonFunctionVariableWarnsAndIsUndef) {
845
845
  EXPECT_NE(warnings.back().find("Ignoring unknown function 'x'"), std::string::npos);
846
846
  }
847
847
 
848
- TEST(Intersection, EchoInDisabledFirstStatementStillFiresDespiteEmptyResult) {
849
- // A deliberate resolve/generate-split consequence: resolve can't yet
850
- // know the whole intersection() will end up geometrically empty (that
851
- // isn't known until generate time), so echo()'s side effect during
852
- // resolve still fires even though the final result has zero bodies.
848
+ TEST(Intersection, EchoInADisabledStatementStillFires) {
849
+ // echo() runs during resolve, so its side effect happens whatever the
850
+ // geometry does. The result is the cube(2): neither the disabled
851
+ // statement nor the echo statement is an operand.
853
852
  std::string captured;
854
853
  Evaluated e = evalSrc("intersection() { *cube(10); echo(\"fired\"); cube(2); }", [&](const std::string& msg) { captured = msg; });
855
854
  EXPECT_EQ(captured, "ECHO: \"fired\"");
856
- EXPECT_TRUE(e.bodies.empty());
855
+ ASSERT_EQ(e.bodies.size(), 1u);
856
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 8.0, 1e-6);
857
857
  }
858
858
 
859
859
  TEST(ListComprehension, IfClauseWithNestedListLiteralBody) {
@@ -91,7 +91,7 @@ TEST(ExportStl, NoGeometryThrows) {
91
91
  TEST(ToRenderableBodies, ThinExtrudesABareTopLevel2dShapeSoItCanExportAsStl) {
92
92
  // A bare `circle();` (no `body`, only `section`) would make writeStl
93
93
  // throw "No geometry to export" -- toRenderableBodies() converts it
94
- // into a thin (1e-3 unit tall) Manifold first, mirroring the reference
94
+ // into a 1-unit-tall Manifold first, mirroring the reference
95
95
  // CLI's own to_renderable_bodies() call right before export. Without
96
96
  // this step, no top-level 2D-only script could ever export a mesh.
97
97
  std::vector<ColoredBody> bodies = evalToBodies("circle(2, $fn=32);");
@@ -104,7 +104,9 @@ TEST(ToRenderableBodies, ThinExtrudesABareTopLevel2dShapeSoItCanExportAsStl) {
104
104
  ASSERT_TRUE(renderable[0].body.has_value());
105
105
  EXPECT_TRUE(renderable[0].flatPreview);
106
106
  manifold::Box bbox = renderable[0].body->BoundingBox();
107
- EXPECT_NEAR(bbox.max.z - bbox.min.z, 1e-3, 1e-9);
107
+ // 1 unit, matching what the reference extrudes a 2D preview to --
108
+ // measured against OpenSCAD at three camera tilts.
109
+ EXPECT_NEAR(bbox.max.z - bbox.min.z, 1.0, 1e-9);
108
110
 
109
111
  const std::string path = tempPath("flat_preview.stl").string();
110
112
  writeStl(path, renderable);