openscad-cpp-evaluator 0.55.4__tar.gz → 1.0.1__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.4 → openscad_cpp_evaluator-1.0.1}/CLAUDE.md +25 -8
  2. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/bytecode.hpp +6 -0
  4. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/colored_body.hpp +17 -10
  5. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/transforms.cpp +156 -5
  7. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/bytecode_compiler.cpp +8 -6
  8. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/bytecode_vm.cpp +6 -0
  9. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/colored_body.cpp +4 -4
  10. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/stmt_eval.cpp +10 -2
  11. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_booleans.cpp +103 -12
  12. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_control_flow.cpp +33 -0
  13. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/uv.lock +1 -1
  14. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/.github/workflows/ci.yml +0 -0
  15. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/.github/workflows/wheels.yml +0 -0
  16. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/.gitignore +0 -0
  17. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/.gitmodules +0 -0
  18. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/CMakeLists.txt +0 -0
  19. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/LICENSE +0 -0
  20. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/README.md +0 -0
  21. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/bindings/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/bindings/ast_to_py.cpp +0 -0
  23. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/bindings/module.cpp +0 -0
  24. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/cmake/embed_font.cmake +0 -0
  25. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/examples/CMakeLists.txt +0 -0
  26. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/examples/eval_perf_benchmark.cpp +0 -0
  27. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/examples/manifold_cache_reuse.cpp +0 -0
  28. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/examples/minimal_debugger.cpp +0 -0
  29. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  30. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/.gitignore +0 -0
  31. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  32. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  33. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/README.md +0 -0
  34. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  35. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  36. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  37. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  38. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  39. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  40. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  41. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  42. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  43. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  44. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  45. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  46. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  47. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  48. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  49. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  50. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/api.cpp +0 -0
  51. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  52. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  53. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  54. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  55. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  56. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  57. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  58. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  59. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  60. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  61. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  62. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  63. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  64. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  65. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  66. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  67. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/position.cpp +0 -0
  68. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  69. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  70. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  71. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  72. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  73. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  74. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  75. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  76. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  77. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  78. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  79. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  80. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  81. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  82. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  83. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  84. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  85. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  86. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  87. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  88. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  89. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  90. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  91. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  92. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  93. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  94. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  95. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  96. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  97. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  98. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  99. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  100. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  101. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  102. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  103. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  104. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  105. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  106. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  107. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  108. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  109. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  110. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/export.hpp +0 -0
  111. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  112. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  113. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  114. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  115. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  116. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  117. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  118. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  119. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  120. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  121. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  122. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  123. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  124. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  125. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/value.hpp +0 -0
  126. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  127. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/python/openscad_cpp_evaluator/__init__.py +0 -0
  128. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/resources/fonts/LICENSE +0 -0
  129. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  130. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/CMakeLists.txt +0 -0
  131. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/booleans.cpp +0 -0
  132. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/builtins.hpp +0 -0
  133. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/call_args.cpp +0 -0
  134. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/color.cpp +0 -0
  135. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/control.cpp +0 -0
  136. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/dxf_dim.cpp +0 -0
  137. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/extrude.cpp +0 -0
  138. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/function_builtins.cpp +0 -0
  139. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/import.cpp +0 -0
  140. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/modifiers.cpp +0 -0
  141. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/primitives_2d.cpp +0 -0
  142. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/primitives_3d.cpp +0 -0
  143. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/registry.cpp +0 -0
  144. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/roof.cpp +0 -0
  145. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/segments.cpp +0 -0
  146. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/surface.cpp +0 -0
  147. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/text.cpp +0 -0
  148. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/builtins/topology.cpp +0 -0
  149. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/csg_generate.cpp +0 -0
  150. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/csg_resolve.cpp +0 -0
  151. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/css_colors.cpp +0 -0
  152. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/debug/debug_repl.cpp +0 -0
  153. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/debug_profile.cpp +0 -0
  154. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/eval_context.cpp +0 -0
  155. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/eval_error.cpp +0 -0
  156. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/eval_use.cpp +0 -0
  157. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/export.cpp +0 -0
  158. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/expr_eval.cpp +0 -0
  159. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/import/dxf_import.cpp +0 -0
  160. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/import/mesh_import.cpp +0 -0
  161. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/import/surface_load.cpp +0 -0
  162. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/import/svg_import.cpp +0 -0
  163. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/manifold_cache.cpp +0 -0
  164. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/measure_geometry.cpp +0 -0
  165. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/mesh_check.cpp +0 -0
  166. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/text/font_match.cpp +0 -0
  167. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/text/freetype_font_provider.cpp +0 -0
  168. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/text/text_metrics.cpp +0 -0
  169. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/user_calls.cpp +0 -0
  170. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/value.cpp +0 -0
  171. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/src/zip_stored.cpp +0 -0
  172. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/CMakeLists.txt +0 -0
  173. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_bytecode_compiler.cpp +0 -0
  174. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_children_separate.cpp +0 -0
  175. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_cli.cpp +0 -0
  176. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_csg_tree.cpp +0 -0
  177. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_debug_hooks.cpp +0 -0
  178. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_dxf_svg_import.cpp +0 -0
  179. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_eval_context.cpp +0 -0
  180. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_export.cpp +0 -0
  181. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_expr_eval.cpp +0 -0
  182. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_extrude_roof.cpp +0 -0
  183. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_feature_detection.cpp +0 -0
  184. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_function_builtins.cpp +0 -0
  185. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_helpers.hpp +0 -0
  186. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_import_export.cpp +0 -0
  187. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_manifold_cache.cpp +0 -0
  188. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_mesh_check.cpp +0 -0
  189. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_multi_color_merge.cpp +0 -0
  190. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_primitives.cpp +0 -0
  191. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_profiling.cpp +0 -0
  192. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_python_bindings.py +0 -0
  193. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_render_expr.cpp +0 -0
  194. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_scope_trail.cpp +0 -0
  195. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_scoping.cpp +0 -0
  196. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_surface.cpp +0 -0
  197. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_tail_calls.cpp +0 -0
  198. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_text.cpp +0 -0
  199. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_transforms.cpp +0 -0
  200. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_unexpected_args.cpp +0 -0
  201. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_use.cpp +0 -0
  202. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_value.cpp +0 -0
  203. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_viewport_params.cpp +0 -0
  204. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tests/test_zip_stored.cpp +0 -0
  205. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tools/cli/CMakeLists.txt +0 -0
  206. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tools/cli/cli_lib.cpp +0 -0
  207. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tools/cli/cli_lib.hpp +0 -0
  208. {openscad_cpp_evaluator-0.55.4 → openscad_cpp_evaluator-1.0.1}/tools/cli/main.cpp +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.4
3
+ Version: 1.0.1
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
@@ -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.4"
7
+ version = "1.0.1"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -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
  }
@@ -1501,15 +1501,17 @@ public:
1501
1501
  // form's own compile case (AssertOp's sibling, above in
1502
1502
  // compileExpr) -- already statement-shaped (no value
1503
1503
  // pushed, straight fall-through), so nothing new is needed
1504
- // there. `.children` is deliberately never read here,
1505
- // matching evalStatement's own ModularEcho case (doEcho
1506
- // only ever takes `.arguments` -- an echo statement's
1507
- // trailing children, if the grammar even produces any, are
1508
- // not evaluated today; this mirrors that exactly rather
1509
- // 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.
1510
1511
  auto& n = static_cast<const oscad::ModularEcho&>(stmt);
1511
1512
  out.push_back({Op::CheckDebugStatement, internNativeStatement(&stmt), 0, nullptr});
1512
1513
  CompiledChunk::EchoSite site;
1514
+ site.node = &n; // statement form: it can have children
1513
1515
  CompileScope exprScope;
1514
1516
  for (const auto& argPtr : n.arguments) {
1515
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
  }
@@ -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;
@@ -731,7 +731,6 @@ TEST(MixedDimensions, ATranslatedTwoDeeShapeKeepsItsZ) {
731
731
  Evaluated e = evalSrc("translate([0,0,-1]) square(10, center=true);");
732
732
  ASSERT_EQ(e.bodies.size(), 1u);
733
733
  ASSERT_TRUE(e.bodies[0].section.has_value());
734
- EXPECT_DOUBLE_EQ(e.bodies[0].sectionZ, -1.0);
735
734
 
736
735
  const std::vector<ColoredBody> renderable = toRenderableBodies(e.bodies);
737
736
  ASSERT_EQ(renderable.size(), 1u);
@@ -740,23 +739,115 @@ TEST(MixedDimensions, ATranslatedTwoDeeShapeKeepsItsZ) {
740
739
  EXPECT_NEAR(box.min.z, -1.0, 1e-9) << "the extruded slab sits where it was moved to";
741
740
  }
742
741
 
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);
742
+ TEST(MixedDimensions, ARotatedTwoDeeShapeLeavesTheXYPlane) {
743
+ // `rotate([90,0,0]) square(10)` stands the square up. A CrossSection
744
+ // cannot hold that either, and dropping it left BOSL2's `rotate($vpr)`
745
+ // labels lying flat -- which read as blobs once the preview slab became
746
+ // a full unit tall rather than a 1e-3 sliver.
747
+ Evaluated e = evalSrc("rotate([90,0,0]) square(10);");
748
+ const std::vector<ColoredBody> renderable = toRenderableBodies(e.bodies);
749
+ ASSERT_EQ(renderable.size(), 1u);
750
+ const manifold::Box box = renderable[0].body->BoundingBox();
751
+ // Standing up: 10 tall in Z, and flat (the slab thickness) in Y.
752
+ EXPECT_NEAR(box.max.z - box.min.z, 10.0, 1e-6);
753
+ EXPECT_NEAR(box.max.y - box.min.y, 1.0, 1e-6) << "the 1-unit slab is now the Y extent";
754
+ EXPECT_NEAR(box.max.x - box.min.x, 10.0, 1e-6);
755
+ }
747
756
 
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);
757
+ TEST(MixedDimensions, TwoDeeTransformsComposeInSourceOrder) {
758
+ // The outer translate must act AFTER the rotation, not before it: with
759
+ // the rotation carried separately, applying a later transform to the
760
+ // CrossSection would silently reorder them.
761
+ Evaluated e = evalSrc("translate([0,0,7]) rotate([90,0,0]) square(10);");
762
+ const std::vector<ColoredBody> renderable = toRenderableBodies(e.bodies);
763
+ ASSERT_EQ(renderable.size(), 1u);
764
+ const manifold::Box box = renderable[0].body->BoundingBox();
765
+ EXPECT_NEAR(box.min.z, 7.0, 1e-6) << "lifted after standing up";
766
+ EXPECT_NEAR(box.max.z, 17.0, 1e-6);
767
+ }
752
768
 
753
- // x/y still go into the CrossSection itself, not the offset.
769
+ TEST(MixedDimensions, PlanarTransformsStillGoIntoTheCrossSection) {
770
+ // Anything the CrossSection CAN hold still goes there, so 2D booleans
771
+ // and offset() keep working on real 2D geometry.
754
772
  Evaluated moved = evalSrc("translate([4,5,6]) square(10);");
755
773
  ASSERT_EQ(moved.bodies.size(), 1u);
756
- EXPECT_DOUBLE_EQ(moved.bodies[0].sectionZ, 6.0);
774
+ ASSERT_TRUE(moved.bodies[0].section.has_value());
757
775
  const manifold::Rect r = moved.bodies[0].section->Bounds();
758
- EXPECT_NEAR(r.min.x, 4.0, 1e-9);
776
+ EXPECT_NEAR(r.min.x, 4.0, 1e-9) << "x/y went into the section itself";
759
777
  EXPECT_NEAR(r.min.y, 5.0, 1e-9);
778
+
779
+ // ...and the z rode along, so the extruded slab still lands at 6.
780
+ const std::vector<ColoredBody> renderable = toRenderableBodies(moved.bodies);
781
+ EXPECT_NEAR(renderable[0].body->BoundingBox().min.z, 6.0, 1e-9);
782
+
783
+ // Two stacked z offsets accumulate rather than the last one winning.
784
+ Evaluated stacked = evalSrc("translate([0,0,2]) translate([0,0,3]) square(10);");
785
+ const std::vector<ColoredBody> r2 = toRenderableBodies(stacked.bodies);
786
+ EXPECT_NEAR(r2[0].body->BoundingBox().min.z, 5.0, 1e-9);
787
+ }
788
+
789
+ // -- transforms on a display-only (open surface) body ---------------------
790
+
791
+ namespace {
792
+ // A cube with one face left off: Manifold rejects it, so it survives only
793
+ // as ColoredBody::rawMesh -- the shape that ignored every transform.
794
+ constexpr const char* kOpenBox =
795
+ "polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0],"
796
+ " [0,0,10],[10,0,10],[10,10,10],[0,10,10]],"
797
+ " faces=[[0,1,2,3],[4,5,1,0],[5,6,2,1],[6,7,3,2],[7,4,0,3]]);";
798
+
799
+ std::array<double, 6> rawBounds(const Evaluated& e) {
800
+ std::array<double, 6> b{1e30, 1e30, 1e30, -1e30, -1e30, -1e30};
801
+ for (const ColoredBody& cb : e.bodies) {
802
+ if (!cb.rawMesh) continue;
803
+ const auto& mesh = *cb.rawMesh;
804
+ for (size_t i = 0; i + 2 < mesh.vertProperties.size(); i += mesh.numProp) {
805
+ for (int k = 0; k < 3; ++k) {
806
+ b[static_cast<size_t>(k)] = std::min(b[static_cast<size_t>(k)],
807
+ static_cast<double>(mesh.vertProperties[i + static_cast<size_t>(k)]));
808
+ b[static_cast<size_t>(k + 3)] = std::max(b[static_cast<size_t>(k + 3)],
809
+ static_cast<double>(mesh.vertProperties[i + static_cast<size_t>(k)]));
810
+ }
811
+ }
812
+ }
813
+ return b;
814
+ }
815
+ } // namespace
816
+
817
+ TEST(DisplayOnlyTransforms, RawTrianglesFollowTheSameTransformAsASolid) {
818
+ // The open box and a closed cube occupy the same 10-cube, so after the
819
+ // SAME transform their bounding boxes must still agree. They did not:
820
+ // the raw triangles were left where they were built, so every copy of
821
+ // an open surface stacked at the origin -- BOSL2's skin() style figure
822
+ // showed one shape where the reference shows seven.
823
+ const char* xforms[] = {
824
+ "translate([5,-3,2])",
825
+ "rotate([30,40,50])",
826
+ "scale([2,0.5,1.5])",
827
+ "mirror([1,1,0])",
828
+ "multmatrix([[1,0.2,0,4],[0,1,0,5],[0,0,1,6]])",
829
+ "rotate(35, v=[1,2,3])",
830
+ };
831
+ for (const char* x : xforms) {
832
+ Evaluated open = evalSrc(std::string(x) + " " + kOpenBox);
833
+ Evaluated solid = evalSrc(std::string(x) + " cube(10);");
834
+ ASSERT_EQ(solid.bodies.size(), 1u) << x;
835
+ const manifold::Box want = solid.bodies[0].body->BoundingBox();
836
+ const std::array<double, 6> got = rawBounds(open);
837
+ EXPECT_NEAR(got[0], want.min.x, 1e-4) << x;
838
+ EXPECT_NEAR(got[1], want.min.y, 1e-4) << x;
839
+ EXPECT_NEAR(got[2], want.min.z, 1e-4) << x;
840
+ EXPECT_NEAR(got[3], want.max.x, 1e-4) << x;
841
+ EXPECT_NEAR(got[4], want.max.y, 1e-4) << x;
842
+ EXPECT_NEAR(got[5], want.max.z, 1e-4) << x;
843
+ }
844
+ }
845
+
846
+ TEST(DisplayOnlyTransforms, EachCopyLandsSomewhereDifferent) {
847
+ Evaluated e = evalSrc(std::string("for (i = [0:2]) translate([20*i, 0, 0]) ") + kOpenBox);
848
+ const std::array<double, 6> b = rawBounds(e);
849
+ EXPECT_NEAR(b[0], 0.0, 1e-6);
850
+ EXPECT_NEAR(b[3], 50.0, 1e-6) << "three copies 20 apart span 0..50, not one box";
760
851
  }
761
852
 
762
853
  TEST(MixedDimensions, UniformChildrenAreUntouched) {
@@ -845,6 +845,38 @@ TEST(ExprEvalFunctionCall, CallingANonFunctionVariableWarnsAndIsUndef) {
845
845
  EXPECT_NE(warnings.back().find("Ignoring unknown function 'x'"), std::string::npos);
846
846
  }
847
847
 
848
+ TEST(EchoStatement, RendersItsChildren) {
849
+ // `echo("x") cube();` -- echo used as a statement PREFIX. The reference
850
+ // echoes and then renders the child; we echoed and dropped it, on the
851
+ // stated assumption that the grammar never produced children here. It
852
+ // does. BOSL2's debug_region() draws its whole region through
853
+ // `for(...) echo(...) linear_extrude(...) region(...)` and came out as
854
+ // labels with nothing under them.
855
+ const char* cases[] = {
856
+ "echo(\"hi\") cube(10);",
857
+ "echo(\"hi\") { cube(10); }",
858
+ "echo(\"a\") echo(\"b\") cube(10);",
859
+ "for (i = [0:1]) echo(\"hi\") translate([i*20,0,0]) cube(10);",
860
+ "union() { echo(\"hi\") cube(10); }",
861
+ };
862
+ for (const char* src : cases) {
863
+ Evaluated e = evalSrc(src);
864
+ const bool anyVolume = std::any_of(
865
+ e.bodies.begin(), e.bodies.end(),
866
+ [](const ColoredBody& b) { return b.body && !b.body->IsEmpty(); });
867
+ EXPECT_TRUE(anyVolume) << src;
868
+ }
869
+ }
870
+
871
+ TEST(EchoStatement, StillEchoesAndKeepsArgumentsSeparateFromChildren) {
872
+ std::string captured;
873
+ Evaluated e = evalSrc("echo(\"fired\") cube(2);",
874
+ [&](const std::string& msg) { captured = msg; });
875
+ EXPECT_EQ(captured, "ECHO: \"fired\"");
876
+ ASSERT_EQ(e.bodies.size(), 1u);
877
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 8.0, 1e-6);
878
+ }
879
+
848
880
  TEST(Intersection, EchoInADisabledStatementStillFires) {
849
881
  // echo() runs during resolve, so its side effect happens whatever the
850
882
  // geometry does. The result is the cube(2): neither the disabled
@@ -1761,3 +1793,4 @@ TEST(RangeDirection, EveryRangeBuildingConstructWarns) {
1761
1793
  << "returned from a function";
1762
1794
  }
1763
1795
 
1796
+
@@ -55,7 +55,7 @@ wheels = [
55
55
 
56
56
  [[package]]
57
57
  name = "openscad-cpp-evaluator"
58
- version = "0.51.3"
58
+ version = "1.0.1"
59
59
  source = { editable = "." }
60
60
  dependencies = [
61
61
  { name = "numpy" },