openscad-cpp-evaluator 0.55.3__tar.gz → 0.55.5__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.3 → openscad_cpp_evaluator-0.55.5}/CLAUDE.md +25 -8
  2. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/bytecode.hpp +6 -0
  4. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/bytecode_vm.hpp +3 -0
  5. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/colored_body.hpp +17 -10
  6. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/pyproject.toml +1 -1
  7. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/python/openscad_cpp_evaluator/__init__.py +2 -1
  8. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/booleans.cpp +47 -0
  9. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/transforms.cpp +156 -5
  10. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/bytecode_compiler.cpp +23 -7
  11. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/bytecode_vm.cpp +12 -1
  12. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/colored_body.cpp +4 -4
  13. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/stmt_eval.cpp +10 -2
  14. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_booleans.cpp +181 -20
  15. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_control_flow.cpp +39 -6
  16. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/.github/workflows/ci.yml +0 -0
  17. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/.github/workflows/wheels.yml +0 -0
  18. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/.gitignore +0 -0
  19. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/.gitmodules +0 -0
  20. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/CMakeLists.txt +0 -0
  21. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/LICENSE +0 -0
  22. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/README.md +0 -0
  23. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/bindings/CMakeLists.txt +0 -0
  24. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/bindings/ast_to_py.cpp +0 -0
  25. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/bindings/module.cpp +0 -0
  26. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/cmake/embed_font.cmake +0 -0
  27. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/examples/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/examples/eval_perf_benchmark.cpp +0 -0
  29. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/examples/manifold_cache_reuse.cpp +0 -0
  30. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/examples/minimal_debugger.cpp +0 -0
  31. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  32. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/.gitignore +0 -0
  33. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  34. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  35. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/README.md +0 -0
  36. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  37. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  38. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  39. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  40. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  41. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  42. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  43. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  44. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  45. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  46. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  47. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  48. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  49. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  50. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  51. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  52. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/api.cpp +0 -0
  53. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  54. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  55. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  56. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  57. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  58. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  59. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  60. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  61. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  62. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  63. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  64. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  65. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  66. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  67. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  68. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  69. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/position.cpp +0 -0
  70. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  71. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  72. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  73. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  74. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  75. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  76. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  77. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  78. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  79. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  80. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  81. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  82. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  83. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  84. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  85. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  86. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  87. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  88. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  89. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  90. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  91. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  92. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  93. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  94. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  95. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  96. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  97. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  98. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  99. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  100. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  101. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  102. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  103. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  104. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  105. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  106. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  107. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  108. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  109. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  110. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  111. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/export.hpp +0 -0
  112. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  113. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  114. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  116. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  117. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  118. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  119. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  120. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  121. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  122. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  123. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  124. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  125. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  126. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/value.hpp +0 -0
  127. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  128. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/resources/fonts/LICENSE +0 -0
  129. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  130. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/CMakeLists.txt +0 -0
  131. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/builtins.hpp +0 -0
  132. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/call_args.cpp +0 -0
  133. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/color.cpp +0 -0
  134. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/control.cpp +0 -0
  135. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/dxf_dim.cpp +0 -0
  136. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/extrude.cpp +0 -0
  137. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/function_builtins.cpp +0 -0
  138. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/import.cpp +0 -0
  139. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/modifiers.cpp +0 -0
  140. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/primitives_2d.cpp +0 -0
  141. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/primitives_3d.cpp +0 -0
  142. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/registry.cpp +0 -0
  143. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/roof.cpp +0 -0
  144. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/segments.cpp +0 -0
  145. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/surface.cpp +0 -0
  146. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/text.cpp +0 -0
  147. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/builtins/topology.cpp +0 -0
  148. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/csg_generate.cpp +0 -0
  149. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/csg_resolve.cpp +0 -0
  150. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/css_colors.cpp +0 -0
  151. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/debug/debug_repl.cpp +0 -0
  152. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/debug_profile.cpp +0 -0
  153. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/eval_context.cpp +0 -0
  154. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/eval_error.cpp +0 -0
  155. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/eval_use.cpp +0 -0
  156. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/export.cpp +0 -0
  157. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/expr_eval.cpp +0 -0
  158. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/import/dxf_import.cpp +0 -0
  159. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/import/mesh_import.cpp +0 -0
  160. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/import/surface_load.cpp +0 -0
  161. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/import/svg_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/manifold_cache.cpp +0 -0
  163. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/measure_geometry.cpp +0 -0
  164. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/mesh_check.cpp +0 -0
  165. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/text/font_match.cpp +0 -0
  166. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/text/freetype_font_provider.cpp +0 -0
  167. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/text/text_metrics.cpp +0 -0
  168. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/user_calls.cpp +0 -0
  169. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/value.cpp +0 -0
  170. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/src/zip_stored.cpp +0 -0
  171. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/CMakeLists.txt +0 -0
  172. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_bytecode_compiler.cpp +0 -0
  173. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_children_separate.cpp +0 -0
  174. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_cli.cpp +0 -0
  175. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_csg_tree.cpp +0 -0
  176. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_debug_hooks.cpp +0 -0
  177. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_dxf_svg_import.cpp +0 -0
  178. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_eval_context.cpp +0 -0
  179. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_export.cpp +0 -0
  180. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_expr_eval.cpp +0 -0
  181. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_extrude_roof.cpp +0 -0
  182. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_feature_detection.cpp +0 -0
  183. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_function_builtins.cpp +0 -0
  184. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_helpers.hpp +0 -0
  185. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_import_export.cpp +0 -0
  186. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_manifold_cache.cpp +0 -0
  187. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_mesh_check.cpp +0 -0
  188. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_multi_color_merge.cpp +0 -0
  189. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_primitives.cpp +0 -0
  190. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_profiling.cpp +0 -0
  191. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_python_bindings.py +0 -0
  192. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_render_expr.cpp +0 -0
  193. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_scope_trail.cpp +0 -0
  194. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_scoping.cpp +0 -0
  195. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_surface.cpp +0 -0
  196. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_tail_calls.cpp +0 -0
  197. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_text.cpp +0 -0
  198. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_transforms.cpp +0 -0
  199. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_unexpected_args.cpp +0 -0
  200. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_use.cpp +0 -0
  201. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_value.cpp +0 -0
  202. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_viewport_params.cpp +0 -0
  203. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tests/test_zip_stored.cpp +0 -0
  204. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tools/cli/CMakeLists.txt +0 -0
  205. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tools/cli/cli_lib.cpp +0 -0
  206. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tools/cli/cli_lib.hpp +0 -0
  207. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/tools/cli/main.cpp +0 -0
  208. {openscad_cpp_evaluator-0.55.3 → openscad_cpp_evaluator-0.55.5}/uv.lock +0 -0
@@ -231,14 +231,31 @@ 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.
234
+ `ColoredBody::sectionXform` rides alongside for the same reason. A `manifold::CrossSection` is
235
+ purely 2D, so `down(1) square(10)` had nowhere to put the `-1` and `rotate([55,0,25]) text("Foo")`
236
+ had nowhere to put the rotation — both were silently dropped, and neither is cosmetic: BOSL2 layers
237
+ 2D overlays by pushing one down (isosurface's `contour()` Example 1 puts a blue backdrop below a
238
+ green contour, and the two slabs became exactly coplanar and z-fought), and turns 2D labels to face
239
+ the camera with `rotate($vpr)` (skin's style figure, which came out lying flat and reading as a blob
240
+ once the preview slab became a full unit tall). `generateTransform` accumulates whatever the
241
+ CrossSection cannot hold into that matrix and `toRenderableBodies()` applies it to the extruded
242
+ slab.
243
+
244
+ Two rules keep the ordering honest. A transform the CrossSection *can* hold still goes into it, so
245
+ 2D booleans and `offset()` keep operating on real 2D geometry — but only while the carried matrix is
246
+ still identity, since applying an outer transform to the section after an out-of-plane one would
247
+ silently reorder them. A pure `translate` is the one exception: while nothing has yet rotated or
248
+ scaled the section, its x/y goes into the section and only its z rides along, because translations
249
+ commute and that keeps `down(1) square(10)` a real 2D shape. `resize()` is carried by neither — the
250
+ reference passes 2D children through it unchanged.
251
+
252
+ The same "geometry Manifold cannot hold" problem applies to `ColoredBody::rawMesh`, the raw triangles
253
+ of a mesh Manifold rejected (an open surface — a height field, a partial `vnf_vertex_array`). Those
254
+ were left where they were built, so `grid_copies(...) vnf_polyhedron(<open surface>)` stacked every
255
+ copy at the origin and BOSL2's skin() style figure showed one shape where the reference shows seven.
256
+ `transformMatrix3d` (transforms.cpp) gives the affine matrix for a transform and
257
+ `generateTransform` applies it to those vertices too; `resize` returns nullopt there, being a
258
+ function of a bounding box a display-only body does not have.
242
259
 
243
260
  **Font support**: a `FontProvider` abstract interface (`font_provider.hpp`: `resolveFont`/
244
261
  `metrics`/`shapeText`/`glyphInkBounds`/`glyphOutline`) with `FreetypeFontProvider` as the built-in
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.55.3
3
+ Version: 0.55.5
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -15,6 +15,7 @@ class FunctionLiteral;
15
15
  class ModuleDeclaration;
16
16
  class ModularCall;
17
17
  class ModularAssert;
18
+ class ModularEcho;
18
19
  class Expression;
19
20
  } // namespace oscad
20
21
 
@@ -868,6 +869,11 @@ struct CompiledChunk {
868
869
  // unused and misleadingly defaulted.
869
870
  struct EchoSite {
870
871
  std::vector<std::optional<std::string>> argNames;
872
+ // The ModularEcho statement, when this site came from one -- an
873
+ // `echo(...) cube();` renders its child after echoing, exactly as
874
+ // AssertSite::node does for `assert(...) cube();`. Null for the
875
+ // EXPRESSION form (`echo(...) 1+1`), which has no children.
876
+ const oscad::ModularEcho* node = nullptr;
871
877
  };
872
878
 
873
879
  // One Op::CallModule site -- a ModularCall statically resolved (at
@@ -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
 
@@ -25,20 +25,27 @@ struct ColoredBody {
25
25
  std::optional<manifold::CrossSection> section;
26
26
  bool flatPreview = false;
27
27
 
28
- // Where a 2D `section` sits along Z. Only meaningful while `section` is
29
- // set, and applied when toRenderableBodies() extrudes it.
28
+ // What a 2D `section` has been transformed BY that a CrossSection
29
+ // cannot itself hold: a translation along Z, a rotation out of the XY
30
+ // plane, anything with a Z component. Applied when
31
+ // toRenderableBodies() extrudes the section.
30
32
  //
31
33
  // 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
34
+ // nowhere to put the -1 and `rotate([55,0,25]) text("Foo")` had nowhere
35
+ // to put the rotation -- both were silently dropped. Neither is
33
36
  // 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
+ // contour() Example 1), and turns 2D labels to face the camera with
38
+ // `rotate($vpr)` (skin's style figure), which came out lying flat and
39
+ // read as a blob once the preview slab became a full unit tall.
37
40
  //
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;
41
+ // Identity while every transform so far has been 2D-representable --
42
+ // those still go into the CrossSection itself, so 2D booleans and
43
+ // offset() keep working on real 2D geometry. From the first transform
44
+ // that is NOT representable, every later one composes here instead:
45
+ // applying an outer transform to the section after an out-of-plane one
46
+ // would put it in the wrong order.
47
+ manifold::mat3x4 sectionXform{manifold::vec3(1, 0, 0), manifold::vec3(0, 1, 0),
48
+ manifold::vec3(0, 0, 1), manifold::vec3(0, 0, 0)};
42
49
  BodyRole role = BodyRole::Normal;
43
50
  std::optional<std::vector<std::array<float, 4>>> triColors; // per-triangle RGBA, multi-color CSG merges only
44
51
 
@@ -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.3"
7
+ version = "0.55.5"
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());
@@ -83,6 +83,15 @@ manifold::Manifold applyRotate(manifold::Manifold body, const Value& aArg, const
83
83
  return body.Transform(axisAngleMatrix(axis, angle * std::numbers::pi / 180.0));
84
84
  }
85
85
 
86
+ // `a` applied after `b`, both as Manifold's column-major 3x4 affines.
87
+ manifold::mat3x4 compose3x4(const manifold::mat3x4& a, const manifold::mat3x4& b) {
88
+ manifold::mat3x4 out;
89
+ for (int c = 0; c < 3; ++c)
90
+ out[c] = a[0] * b[c].x + a[1] * b[c].y + a[2] * b[c].z;
91
+ out[3] = a[0] * b[3].x + a[1] * b[3].y + a[2] * b[3].z + a[3];
92
+ return out;
93
+ }
94
+
86
95
  // Converts a user-supplied 4x4/4x3 nested-list matrix (row-major, like
87
96
  // every OpenSCAD-facing representation) to Manifold's column-major 3x4
88
97
  // transform. Mirrors _to_matrix4x3.
@@ -117,6 +126,121 @@ manifold::mat2x3 toMat2x3(const Value& m) {
117
126
  return manifold::mat2x3(col0, col1, col2);
118
127
  }
119
128
 
129
+ // The affine matrix a transform applies, for geometry Manifold cannot hold.
130
+ //
131
+ // A mesh Manifold rejected (an open surface -- a height field, a partial
132
+ // `vnf_vertex_array`) survives only as ColoredBody::rawMesh, raw triangles
133
+ // with no Manifold to transform. Those triangles were left where they were
134
+ // built, so `grid_copies(...) vnf_polyhedron(<open surface>)` stacked every
135
+ // copy at the origin and BOSL2's skin() style figure showed one shape where
136
+ // the reference shows seven.
137
+ //
138
+ // nullopt for `resize`, which is not a fixed map at all: its scale comes
139
+ // from the body's own bounding box, and a display-only body has no Manifold
140
+ // to measure. Verified against Manifold's own operators by
141
+ // TransformMatrix3d tests rather than assumed -- the Euler order especially.
142
+ // True if `m` is a pure translation: nothing has rotated, scaled or
143
+ // mirrored the section, so a further translation commutes with it.
144
+ bool hasIdentityLinearPart(const manifold::mat3x4& m) {
145
+ for (int c = 0; c < 3; ++c) {
146
+ const manifold::vec3 want(c == 0 ? 1.0 : 0.0, c == 1 ? 1.0 : 0.0, c == 2 ? 1.0 : 0.0);
147
+ if (m[c] != want) return false;
148
+ }
149
+ return true;
150
+ }
151
+
152
+ bool isIdentity3x4(const manifold::mat3x4& m) {
153
+ for (int c = 0; c < 4; ++c) {
154
+ const manifold::vec3 want(c == 0 ? 1.0 : 0.0, c == 1 ? 1.0 : 0.0, c == 2 ? 1.0 : 0.0);
155
+ if (m[c] != want) return false;
156
+ }
157
+ return true;
158
+ }
159
+
160
+ // True if `m` maps the XY plane onto itself, so a CrossSection can hold it:
161
+ // nothing leaves the plane (no Z from x or y, no Z offset) and Z maps to Z.
162
+ bool preservesXYPlane(const manifold::mat3x4& m) {
163
+ return m[0].z == 0.0 && m[1].z == 0.0 && m[3].z == 0.0 &&
164
+ m[2].x == 0.0 && m[2].y == 0.0;
165
+ }
166
+
167
+ // Applies `m` to a raw mesh's vertex POSITIONS in place. Any further
168
+ // per-vertex properties (normals and the like) are left alone: nothing
169
+ // reads them off a display-only body, which is drawn from its triangles.
170
+ void transformMeshInPlace(manifold::MeshGL& mesh, const manifold::mat3x4& m) {
171
+ const uint32_t stride = mesh.numProp;
172
+ if (stride < 3) return;
173
+ for (size_t i = 0; i + 2 < mesh.vertProperties.size(); i += stride) {
174
+ const double x = mesh.vertProperties[i];
175
+ const double y = mesh.vertProperties[i + 1];
176
+ const double z = mesh.vertProperties[i + 2];
177
+ const manifold::vec3 p = m[0] * x + m[1] * y + m[2] * z + m[3];
178
+ mesh.vertProperties[i] = static_cast<float>(p.x);
179
+ mesh.vertProperties[i + 1] = static_cast<float>(p.y);
180
+ mesh.vertProperties[i + 2] = static_cast<float>(p.z);
181
+ }
182
+ }
183
+
184
+ std::optional<manifold::mat3x4> transformMatrix3d(const std::string& name, const CallArgs& args) {
185
+ const manifold::mat3x4 ident(manifold::vec3(1, 0, 0), manifold::vec3(0, 1, 0),
186
+ manifold::vec3(0, 0, 1), manifold::vec3(0, 0, 0));
187
+ if (name == "translate") {
188
+ manifold::mat3x4 m = ident;
189
+ m[3] = toVec3(getArg(args, 0, "v", Value{}));
190
+ return m;
191
+ }
192
+ if (name == "rotate") {
193
+ const Value aArg = getArg(args, 0, "a", Value{0.0});
194
+ const Value vArg = getArg(args, 1, "v", Value{});
195
+ if (std::holds_alternative<ListPtr>(aArg)) {
196
+ const manifold::vec3 a = toVec3(aArg);
197
+ const double d2r = std::numbers::pi / 180.0;
198
+ // X, then Y, then Z -- the order Manifold::Rotate uses, which
199
+ // applyTransform3d hands the same angles to.
200
+ const manifold::mat3x4 rx = axisAngleMatrix(manifold::vec3(1, 0, 0), a.x * d2r);
201
+ const manifold::mat3x4 ry = axisAngleMatrix(manifold::vec3(0, 1, 0), a.y * d2r);
202
+ const manifold::mat3x4 rz = axisAngleMatrix(manifold::vec3(0, 0, 1), a.z * d2r);
203
+ return compose3x4(rz, compose3x4(ry, rx));
204
+ }
205
+ const double angle = toDoubleLenient(aArg);
206
+ const manifold::vec3 axis =
207
+ std::holds_alternative<std::monostate>(vArg) ? manifold::vec3(0, 0, 1) : toVec3(vArg);
208
+ return axisAngleMatrix(axis, angle * std::numbers::pi / 180.0);
209
+ }
210
+ if (name == "scale") {
211
+ Value v = getArg(args, 0, "v", Value{1.0});
212
+ manifold::vec3 sv = std::get_if<double>(&v)
213
+ ? manifold::vec3(*std::get_if<double>(&v), *std::get_if<double>(&v),
214
+ *std::get_if<double>(&v))
215
+ : toVec3(v, 1.0, 1.0, 1.0);
216
+ manifold::mat3x4 m = ident;
217
+ m[0] = manifold::vec3(sv.x, 0, 0);
218
+ m[1] = manifold::vec3(0, sv.y, 0);
219
+ m[2] = manifold::vec3(0, 0, sv.z);
220
+ return m;
221
+ }
222
+ if (name == "mirror") {
223
+ manifold::vec3 n = toVec3(getArg(args, 0, "v", Value{}), 1.0, 0.0, 0.0);
224
+ const double len2 = n.x * n.x + n.y * n.y + n.z * n.z;
225
+ if (len2 <= 0.0) return ident;
226
+ manifold::mat3x4 m = ident;
227
+ // I - 2 n n^T / |n|^2, written column by column.
228
+ for (int c = 0; c < 3; ++c) {
229
+ const double nc = c == 0 ? n.x : (c == 1 ? n.y : n.z);
230
+ m[c] = manifold::vec3((c == 0 ? 1.0 : 0.0) - 2.0 * n.x * nc / len2,
231
+ (c == 1 ? 1.0 : 0.0) - 2.0 * n.y * nc / len2,
232
+ (c == 2 ? 1.0 : 0.0) - 2.0 * n.z * nc / len2);
233
+ }
234
+ return m;
235
+ }
236
+ if (name == "multmatrix") {
237
+ Value m = getArg(args, 0, "m", Value{});
238
+ if (std::holds_alternative<std::monostate>(m)) return ident;
239
+ return toMat3x4(m);
240
+ }
241
+ return std::nullopt;
242
+ }
243
+
120
244
  manifold::Manifold applyTransform3d(const std::string& name, const CallArgs& args, manifold::Manifold body) {
121
245
  if (name == "translate") {
122
246
  return body.Translate(toVec3(getArg(args, 0, "v", Value{})));
@@ -233,13 +357,40 @@ std::vector<ColoredBody> generateTransform(Evaluator&, const CSGParams& params,
233
357
  std::vector<ColoredBody> result;
234
358
  for (ColoredBody b : flattenCsgTree(children)) {
235
359
  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;
240
- b.section = applyTransform2d(name, args, std::move(*b.section));
360
+ // A 2D-representable transform goes into the CrossSection, so
361
+ // 2D booleans and offset() keep operating on real 2D geometry.
362
+ // Anything else -- a Z translation, a rotation out of the XY
363
+ // plane -- has nowhere to go there and rides along on the body
364
+ // until the section is extruded. Once one of those has been
365
+ // seen, every LATER (outer) transform must ride along too, or
366
+ // it would be applied in the wrong order. See
367
+ // ColoredBody::sectionXform.
368
+ const std::optional<manifold::mat3x4> m = transformMatrix3d(name, args);
369
+ if (isIdentity3x4(b.sectionXform) && (!m || preservesXYPlane(*m))) {
370
+ b.section = applyTransform2d(name, args, std::move(*b.section));
371
+ } else if (name == "translate" && hasIdentityLinearPart(b.sectionXform)) {
372
+ // A translate SPLITS while nothing has rotated or scaled the
373
+ // section yet: x/y into the CrossSection, z into the carried
374
+ // transform. Translations commute, so the order is safe --
375
+ // and keeping x/y in the section is what lets a later 2D
376
+ // boolean or offset() still see real 2D geometry after a
377
+ // `down(1) square(10)`.
378
+ b.section = applyTransform2d(name, args, std::move(*b.section));
379
+ b.sectionXform[3].z += toVec3(getArg(args, 0, "v", Value{})).z;
380
+ } else if (m) {
381
+ b.sectionXform = compose3x4(*m, b.sectionXform);
382
+ }
383
+ // No `m` and already carrying a transform: only resize(), which
384
+ // the reference passes 2D children through unchanged anyway.
241
385
  } else if (b.body) {
242
386
  b.body = applyTransform3d(name, args, std::move(*b.body));
387
+ // A display-only body's real geometry is its raw triangles --
388
+ // the Manifold above is empty and moving it moves nothing. See
389
+ // transformMatrix3d.
390
+ if (b.rawMesh) {
391
+ if (std::optional<manifold::mat3x4> m = transformMatrix3d(name, args))
392
+ transformMeshInPlace(*b.rawMesh, *m);
393
+ }
243
394
  }
244
395
  result.push_back(std::move(b));
245
396
  }
@@ -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()});
@@ -1487,15 +1501,17 @@ public:
1487
1501
  // form's own compile case (AssertOp's sibling, above in
1488
1502
  // compileExpr) -- already statement-shaped (no value
1489
1503
  // pushed, straight fall-through), so nothing new is needed
1490
- // there. `.children` is deliberately never read here,
1491
- // matching evalStatement's own ModularEcho case (doEcho
1492
- // only ever takes `.arguments` -- an echo statement's
1493
- // trailing children, if the grammar even produces any, are
1494
- // not evaluated today; this mirrors that exactly rather
1495
- // than silently changing behavior).
1504
+ // there. `.children` IS read: `echo("x") cube();` renders the
1505
+ // cube in the reference, and the grammar does produce those
1506
+ // children. They used to be dropped on the assumption that it
1507
+ // never did, which silently lost the geometry -- BOSL2's
1508
+ // debug_region() draws its whole region through
1509
+ // `for(...) echo(...) linear_extrude(...) region(...)` and came
1510
+ // out as labels with nothing under them.
1496
1511
  auto& n = static_cast<const oscad::ModularEcho&>(stmt);
1497
1512
  out.push_back({Op::CheckDebugStatement, internNativeStatement(&stmt), 0, nullptr});
1498
1513
  CompiledChunk::EchoSite site;
1514
+ site.node = &n; // statement form: it can have children
1499
1515
  CompileScope exprScope;
1500
1516
  for (const auto& argPtr : n.arguments) {
1501
1517
  if (argPtr->kind() == NodeKind::NamedArgument) {
@@ -981,6 +981,12 @@ Value driveVm(Evaluator& ev, size_t floor) {
981
981
  f.stack.pop_back();
982
982
  }
983
983
  ev.emitEcho(pairs);
984
+ // The STATEMENT form can carry children and the
985
+ // reference renders them; same tail as
986
+ // Op::AssertStatement below. Null node = the expression
987
+ // form (`echo(...) 1+1`), which has none.
988
+ if (site.node && !site.node->children.empty())
989
+ ev.evalChildren(site.node->children, ctx);
984
990
  ++f.pc;
985
991
  break;
986
992
  }
@@ -1311,7 +1317,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1311
1317
  // ITERATION child ctxs are pushed separately, by the
1312
1318
  // compiled loop's own Op::ForIterNext.
1313
1319
  ev.treeStack_.emplace_back();
1314
- f.csgWrapStack.push_back({site.op, randsBefore, ins.a, {}, 0});
1320
+ f.csgWrapStack.push_back({site.op, randsBefore, ins.a, {}, {}, 0});
1315
1321
  ++f.pc;
1316
1322
  break;
1317
1323
  }
@@ -1336,6 +1342,8 @@ Value driveVm(Evaluator& ev, size_t floor) {
1336
1342
  ev.evalChildren(std::vector<const oscad::ASTNode*>{stmt}, ctx);
1337
1343
  pending.groupSizes.push_back(
1338
1344
  Value{static_cast<double>(ev.treeStack_.back().size() - before)});
1345
+ // A spliced child is a plain statement, never a loop.
1346
+ pending.emptyIsAGroup.push_back(Value{false});
1339
1347
  }
1340
1348
  ++f.pc;
1341
1349
  break;
@@ -1344,6 +1352,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1344
1352
  PendingCsgWrap& pending = f.csgWrapStack.back();
1345
1353
  const size_t after = ev.treeStack_.back().size();
1346
1354
  pending.groupSizes.push_back(Value{static_cast<double>(after - pending.groupStartSize)});
1355
+ pending.emptyIsAGroup.push_back(Value{ins.a != 0});
1347
1356
  ++f.pc;
1348
1357
  break;
1349
1358
  }
@@ -1367,6 +1376,8 @@ Value driveVm(Evaluator& ev, size_t floor) {
1367
1376
  if (site.includeOpParam) params["op"] = Value{pending.op};
1368
1377
  params["group_sizes"] =
1369
1378
  Value{std::make_shared<const ValueList>(ValueList{std::move(pending.groupSizes)})};
1379
+ params["empty_is_a_group"] = Value{std::make_shared<const ValueList>(
1380
+ ValueList{std::move(pending.emptyIsAGroup)})};
1370
1381
  // Mirrors Evaluator::buildTreeNode's own post-
1371
1382
  // resolveBody() half exactly (csg_resolve.cpp).
1372
1383
  const bool uncacheable =
@@ -37,10 +37,10 @@ std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodi
37
37
  if (!cb.body && cb.section) {
38
38
  ColoredBody flat;
39
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));
40
+ // Put it back where the transforms a CrossSection could not
41
+ // hold would have placed it -- a Z offset, a rotation out of
42
+ // the plane. See ColoredBody::sectionXform.
43
+ flat.body = flat.body->Transform(cb.sectionXform);
44
44
  flat.color = cb.color;
45
45
  flat.flatPreview = true;
46
46
  flat.role = cb.role;
@@ -241,9 +241,17 @@ void Evaluator::evalStatement(const oscad::ASTNode& node, EvalContext& ctx) {
241
241
  case oscad::NodeKind::ModularLet:
242
242
  evalLetBlock(static_cast<const oscad::ModularLet&>(node), ctx);
243
243
  return;
244
- case oscad::NodeKind::ModularEcho:
245
- doEcho(static_cast<const oscad::ModularEcho&>(node).arguments, ctx);
244
+ case oscad::NodeKind::ModularEcho: {
245
+ // `echo("x") cube();` echoes AND renders the cube, the way the
246
+ // reference does and the way ModularAssert already did here.
247
+ // Dropping the children silently lost geometry: BOSL2's
248
+ // debug_region() draws its whole region through
249
+ // `for(...) echo(...) linear_extrude(...) region(...)`.
250
+ const auto& e = static_cast<const oscad::ModularEcho&>(node);
251
+ doEcho(e.arguments, ctx);
252
+ if (!e.children.empty()) evalChildren(e.children, ctx);
246
253
  return;
254
+ }
247
255
  case oscad::NodeKind::ModularAssert:
248
256
  evalAssertStatement(static_cast<const oscad::ModularAssert&>(node), ctx);
249
257
  return;