openscad-cpp-evaluator 0.55.1__tar.gz → 0.55.3__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.1 → openscad_cpp_evaluator-0.55.3}/CLAUDE.md +9 -0
  2. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/bindings/ast_to_py.cpp +8 -0
  4. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/bindings/module.cpp +8 -0
  5. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/colored_body.hpp +16 -1
  6. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/evaluator.hpp +2 -1
  7. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/pyproject.toml +1 -1
  8. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/python/openscad_cpp_evaluator/__init__.py +20 -1
  9. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/transforms.cpp +4 -0
  10. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/colored_body.cpp +11 -1
  11. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/csg_generate.cpp +27 -6
  12. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/export.cpp +13 -0
  13. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_booleans.cpp +83 -7
  14. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_export.cpp +4 -2
  15. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_python_bindings.py +37 -0
  16. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/.github/workflows/ci.yml +0 -0
  17. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/.github/workflows/wheels.yml +0 -0
  18. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/.gitignore +0 -0
  19. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/.gitmodules +0 -0
  20. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/CMakeLists.txt +0 -0
  21. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/LICENSE +0 -0
  22. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/README.md +0 -0
  23. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/bindings/CMakeLists.txt +0 -0
  24. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/cmake/embed_font.cmake +0 -0
  25. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/examples/CMakeLists.txt +0 -0
  26. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/examples/eval_perf_benchmark.cpp +0 -0
  27. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/examples/manifold_cache_reuse.cpp +0 -0
  28. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/examples/minimal_debugger.cpp +0 -0
  29. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  30. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/.gitignore +0 -0
  31. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  32. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  33. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/README.md +0 -0
  34. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  35. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  36. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  37. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  38. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  39. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  40. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  41. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  42. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  43. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  44. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  45. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  46. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  47. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  48. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  49. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  50. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/api.cpp +0 -0
  51. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  52. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  53. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  54. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  55. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  56. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  57. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  58. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  59. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  60. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  61. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  62. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  63. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  64. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  65. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  66. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  67. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/position.cpp +0 -0
  68. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  69. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  70. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  71. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  72. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  73. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  74. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  75. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  76. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  77. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  78. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  79. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  80. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  81. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  82. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  83. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  84. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  85. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  86. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  87. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  88. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  89. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  90. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  91. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  92. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  93. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  94. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  95. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  96. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  97. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  98. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  99. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  100. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  101. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  102. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  103. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  104. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  105. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  106. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  107. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  108. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  109. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  110. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/export.hpp +0 -0
  111. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  112. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  113. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  114. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  115. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  116. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  117. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  118. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  119. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  120. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  121. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  122. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  123. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  124. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  125. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/value.hpp +0 -0
  126. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  127. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/resources/fonts/LICENSE +0 -0
  128. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  129. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/CMakeLists.txt +0 -0
  130. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/booleans.cpp +0 -0
  131. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/builtins.hpp +0 -0
  132. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/call_args.cpp +0 -0
  133. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/color.cpp +0 -0
  134. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/control.cpp +0 -0
  135. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/dxf_dim.cpp +0 -0
  136. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/extrude.cpp +0 -0
  137. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/function_builtins.cpp +0 -0
  138. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/import.cpp +0 -0
  139. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/modifiers.cpp +0 -0
  140. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/primitives_2d.cpp +0 -0
  141. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/primitives_3d.cpp +0 -0
  142. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/registry.cpp +0 -0
  143. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/roof.cpp +0 -0
  144. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/segments.cpp +0 -0
  145. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/surface.cpp +0 -0
  146. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/text.cpp +0 -0
  147. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/builtins/topology.cpp +0 -0
  148. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/bytecode_compiler.cpp +0 -0
  149. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/bytecode_vm.cpp +0 -0
  150. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/csg_resolve.cpp +0 -0
  151. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/css_colors.cpp +0 -0
  152. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/debug/debug_repl.cpp +0 -0
  153. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/debug_profile.cpp +0 -0
  154. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/eval_context.cpp +0 -0
  155. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/eval_error.cpp +0 -0
  156. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/eval_use.cpp +0 -0
  157. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/expr_eval.cpp +0 -0
  158. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/import/dxf_import.cpp +0 -0
  159. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/import/mesh_import.cpp +0 -0
  160. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/import/surface_load.cpp +0 -0
  161. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/import/svg_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/manifold_cache.cpp +0 -0
  163. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/measure_geometry.cpp +0 -0
  164. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/mesh_check.cpp +0 -0
  165. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/stmt_eval.cpp +0 -0
  166. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/text/font_match.cpp +0 -0
  167. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/text/freetype_font_provider.cpp +0 -0
  168. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/text/text_metrics.cpp +0 -0
  169. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/user_calls.cpp +0 -0
  170. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/value.cpp +0 -0
  171. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/src/zip_stored.cpp +0 -0
  172. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/CMakeLists.txt +0 -0
  173. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_bytecode_compiler.cpp +0 -0
  174. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_children_separate.cpp +0 -0
  175. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_cli.cpp +0 -0
  176. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_control_flow.cpp +0 -0
  177. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_csg_tree.cpp +0 -0
  178. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_debug_hooks.cpp +0 -0
  179. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_dxf_svg_import.cpp +0 -0
  180. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_eval_context.cpp +0 -0
  181. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_expr_eval.cpp +0 -0
  182. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_extrude_roof.cpp +0 -0
  183. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_feature_detection.cpp +0 -0
  184. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_function_builtins.cpp +0 -0
  185. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_helpers.hpp +0 -0
  186. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_import_export.cpp +0 -0
  187. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_manifold_cache.cpp +0 -0
  188. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_mesh_check.cpp +0 -0
  189. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_multi_color_merge.cpp +0 -0
  190. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_primitives.cpp +0 -0
  191. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_profiling.cpp +0 -0
  192. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_render_expr.cpp +0 -0
  193. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_scope_trail.cpp +0 -0
  194. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_scoping.cpp +0 -0
  195. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_surface.cpp +0 -0
  196. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_tail_calls.cpp +0 -0
  197. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_text.cpp +0 -0
  198. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_transforms.cpp +0 -0
  199. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_unexpected_args.cpp +0 -0
  200. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_use.cpp +0 -0
  201. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_value.cpp +0 -0
  202. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_viewport_params.cpp +0 -0
  203. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tests/test_zip_stored.cpp +0 -0
  204. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tools/cli/CMakeLists.txt +0 -0
  205. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tools/cli/cli_lib.cpp +0 -0
  206. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tools/cli/cli_lib.hpp +0 -0
  207. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/tools/cli/main.cpp +0 -0
  208. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.3}/uv.lock +0 -0
@@ -231,6 +231,15 @@ top-level 2D primitive/projection() script had been silently unexportable via th
231
231
  Fixed by porting `toRenderableBodies()` (`colored_body.hpp`/`.cpp`) and calling it from the CLI right
232
232
  before export, matching the reference's own call site exactly.
233
233
 
234
+ `ColoredBody::sectionZ` rides alongside for the same reason. A `manifold::CrossSection` is purely
235
+ 2D, so `down(1) square(10)` had nowhere to put the `-1` and silently dropped it — and that is not
236
+ cosmetic: BOSL2 layers 2D overlays by pushing one down (isosurface's `contour()` Example 1 puts a
237
+ blue backdrop below a green contour), and with the offset lost the two slabs became exactly
238
+ coplanar, z-fought, and whichever drew last covered the other completely. `generateTransform`
239
+ accumulates the z of any `translate` applied to a section, and `toRenderableBodies()` translates the
240
+ extruded slab by it. Only translation is carried — a 2D shape under a 3D *rotation* still loses it,
241
+ which would need a full matrix per section and nothing has asked for that yet.
242
+
234
243
  **Font support**: a `FontProvider` abstract interface (`font_provider.hpp`: `resolveFont`/
235
244
  `metrics`/`shapeText`/`glyphInkBounds`/`glyphOutline`) with `FreetypeFontProvider` as the built-in
236
245
  implementation everything here actually uses. `Evaluator`'s constructor takes an optional
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.55.1
3
+ Version: 0.55.3
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -25,6 +25,7 @@
25
25
  // losing how the author wrote it (1.500 -> 1.5, 1e3 -> 1000).
26
26
 
27
27
  #include "openscad_cpp_parser/api.hpp"
28
+ #include "openscad_cpp_parser/pretty_print.hpp"
28
29
  #include "openscad_cpp_parser/ast/ast_node.hpp"
29
30
  #include "openscad_cpp_parser/ast/comments.hpp"
30
31
  #include "openscad_cpp_parser/ast/declarations.hpp"
@@ -323,4 +324,11 @@ nb::list parseAstFromString(const std::string& code, bool includeComments) {
323
324
  return astToPy(oscad::getASTFromString(code, includeComments));
324
325
  }
325
326
 
327
+ // The parser already has a pretty-printer -- comment-preserving, idempotent
328
+ // and covered by its own tests -- so an editor's "reformat" needs no second
329
+ // implementation guessing at the same grammar from a token stream.
330
+ std::string formatSourceString(const std::string& code, int indentWidth, bool includeComments) {
331
+ return oscad::toOpenscad(oscad::getASTFromString(code, includeComments), indentWidth);
332
+ }
333
+
326
334
  } // namespace oscadbind
@@ -49,6 +49,7 @@ namespace nb = nanobind;
49
49
  namespace oscadbind {
50
50
  nb::list parseAstFromFile(const std::string& path, bool includeComments);
51
51
  nb::list parseAstFromString(const std::string& code, bool includeComments);
52
+ std::string formatSourceString(const std::string& code, int indentWidth, bool includeComments);
52
53
  } // namespace oscadbind
53
54
 
54
55
  namespace {
@@ -809,6 +810,13 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
809
810
  "safe to keep, share across threads, and outlive the parse.");
810
811
  m.def("parse_ast_string", &oscadbind::parseAstFromString, nb::arg("code"), nb::arg("include_comments") = false,
811
812
  "As parse_ast(), but parses a source string instead of a file.");
813
+ m.def("format_source", &oscadbind::formatSourceString, nb::arg("code"),
814
+ nb::arg("indent") = 4, nb::arg("include_comments") = true,
815
+ "Reformat OpenSCAD source: parse it and print the AST back out. "
816
+ "Wraps over-long argument lists and vectors, puts a modifier's "
817
+ "children on their own indented line, and preserves comments. "
818
+ "Idempotent -- formatting formatted source returns it unchanged. "
819
+ "Raises ParseError if the source does not parse.");
812
820
  m.def("debug_evaluate", &debugEvaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("debug_hook"),
813
821
  nb::arg("error_break"), nb::arg("echo_fn"), nb::arg("manifold_cache") = nullptr,
814
822
  nb::arg("return_hook") = nb::none(), nb::arg("fast_continue_signal") = nullptr,
@@ -24,6 +24,21 @@ struct ColoredBody {
24
24
  std::optional<std::array<float, 4>> color; // RGBA; nullopt = "no explicit color() -- follow the live theme"
25
25
  std::optional<manifold::CrossSection> section;
26
26
  bool flatPreview = false;
27
+
28
+ // Where a 2D `section` sits along Z. Only meaningful while `section` is
29
+ // set, and applied when toRenderableBodies() extrudes it.
30
+ //
31
+ // A manifold::CrossSection is purely 2D, so `down(1) square(10)` had
32
+ // nowhere to put the -1 and silently dropped it. That is not merely
33
+ // cosmetic: BOSL2 layers 2D overlays by pushing one down (isosurface's
34
+ // contour() Example 1 puts a blue backdrop below a green contour), and
35
+ // with the offset lost the two slabs became exactly coplanar, z-fought,
36
+ // and whichever was drawn last covered the other completely.
37
+ //
38
+ // Only translation is carried. A 2D shape under a 3D ROTATION still
39
+ // loses it -- representing that needs a full matrix per section, which
40
+ // nothing has asked for yet.
41
+ double sectionZ = 0.0;
27
42
  BodyRole role = BodyRole::Normal;
28
43
  std::optional<std::vector<std::array<float, 4>>> triColors; // per-triangle RGBA, multi-color CSG merges only
29
44
 
@@ -60,7 +75,7 @@ Value colorToValue(const std::optional<std::array<double, 4>>& color);
60
75
  std::optional<std::array<float, 4>> valueToColor(const Value& v);
61
76
 
62
77
  // Converts every top-level 2D-only result (`body` unset, `section` set --
63
- // e.g. a bare `circle();`) into a thin-extruded (1e-3 units tall) Manifold
78
+ // e.g. a bare `circle();`) into a 1-unit-tall extruded Manifold
64
79
  // tagged `flatPreview = true`, so a mesh exporter/renderer that only
65
80
  // understands 3D Manifolds (writeStl/writeObj/writeOff/writeThreeMf) can
66
81
  // still show a 2D top-level result instead of silently dropping it. 3D
@@ -427,7 +427,8 @@ public:
427
427
  // before its GenerateFn runs -- see csg_generate.cpp.
428
428
  void applyDimensionRules(CSGNode& node);
429
429
  // ruleValue is a DimRule; the enum is file-local to csg_generate.cpp.
430
- void applyDimensionRulesTo(const std::vector<CSGNode*>& children, int ruleValue);
430
+ void applyDimensionRulesTo(const std::vector<CSGNode*>& children, int ruleValue,
431
+ bool keepMixed = false);
431
432
 
432
433
  // Apply `!`: replace the whole tree with the first show_only
433
434
  // subtree, discarding its ancestors and every sibling. See its
@@ -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.1"
7
+ version = "0.55.3"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -18,7 +18,8 @@ from ._openscad_cpp_evaluator import FastContinueSignal, ManifoldCache
18
18
  __all__ = [
19
19
  "Evaluator", "ColoredBody", "EvalError", "ParseError", "OscObject", "parse", "to_renderable_bodies",
20
20
  "ManifoldCache", "CallSiteProfile", "ProfileResult", "format_csg_tree", "bodies_from_dicts",
21
- "FastContinueSignal", "parse_ast", "parse_ast_string", "check_mesh", "strip_slivers",
21
+ "FastContinueSignal", "parse_ast", "parse_ast_string", "format_source",
22
+ "check_mesh", "strip_slivers",
22
23
  "export_model", "export_extensions",
23
24
  ]
24
25
 
@@ -376,6 +377,24 @@ def parse_ast_string(code: str, include_comments: bool = False) -> list:
376
377
  raise ParseError(str(e)) from e
377
378
 
378
379
 
380
+ def format_source(code: str, indent: int = 4, include_comments: bool = True) -> str:
381
+ """Reformat OpenSCAD source: parse it and print the AST back out.
382
+
383
+ Wraps over-long argument lists and vector literals, puts a modifier's
384
+ children on their own indented line, and preserves comments. Idempotent
385
+ -- formatting already-formatted source returns it unchanged. Raises
386
+ ParseError on a syntax error.
387
+
388
+ This is the parser's own printer (`oscad::toOpenscad`), so an editor
389
+ reformatting a selection does not need a second implementation guessing
390
+ at the same grammar from a token stream.
391
+ """
392
+ try:
393
+ return _ext.format_source(code, indent, include_comments)
394
+ except Exception as e:
395
+ raise ParseError(str(e)) from e
396
+
397
+
379
398
  class _MeshShim:
380
399
  """Duck-types a manifold3d mesh: the four arrays the renderer reads."""
381
400
  __slots__ = ("vert_properties", "tri_verts", "run_original_id", "run_index", "num_prop")
@@ -233,6 +233,10 @@ std::vector<ColoredBody> generateTransform(Evaluator&, const CSGParams& params,
233
233
  std::vector<ColoredBody> result;
234
234
  for (ColoredBody b : flattenCsgTree(children)) {
235
235
  if (b.section) {
236
+ // The CrossSection takes x/y; z has nowhere to go in it, so it
237
+ // rides along on the body until the section is extruded. See
238
+ // ColoredBody::sectionZ.
239
+ if (name == "translate") b.sectionZ += toVec3(getArg(args, 0, "v", Value{})).z;
236
240
  b.section = applyTransform2d(name, args, std::move(*b.section));
237
241
  } else if (b.body) {
238
242
  b.body = applyTransform3d(name, args, std::move(*b.body));
@@ -21,7 +21,13 @@ std::optional<std::array<float, 4>> valueToColor(const Value& v) {
21
21
  }
22
22
 
23
23
  namespace {
24
- constexpr double kTopLevel2dHeight = 1e-3;
24
+ // The reference extrudes a 2D shape to exactly 1 unit for preview -- measured
25
+ // against OpenSCAD at three camera tilts (60/70/80 degrees), which put it at
26
+ // 0.92/1.00/0.99, the 60 being pixel-rounding on the shallowest angle. Ours
27
+ // was 1e-3, which reads as a flat silhouette with no visible edge and, more
28
+ // annoyingly, gives --viewall a different bounding box to fit, so every 2D
29
+ // docs image came out at a different scale from the published one.
30
+ constexpr double kTopLevel2dHeight = 1.0;
25
31
  } // namespace
26
32
 
27
33
  std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodies) {
@@ -31,6 +37,10 @@ std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodi
31
37
  if (!cb.body && cb.section) {
32
38
  ColoredBody flat;
33
39
  flat.body = manifold::Manifold::Extrude(cb.section->ToPolygons(), kTopLevel2dHeight);
40
+ // Put it back where a 3D translate moved it -- the CrossSection
41
+ // could not hold that. See ColoredBody::sectionZ.
42
+ if (cb.sectionZ != 0.0)
43
+ flat.body = flat.body->Translate(manifold::vec3(0, 0, cb.sectionZ));
34
44
  flat.color = cb.color;
35
45
  flat.flatPreview = true;
36
46
  flat.role = cb.role;
@@ -171,9 +171,10 @@ void Evaluator::applyDimensionRules(CSGNode& node) {
171
171
  applyDimensionRulesTo(kids, static_cast<int>(rule));
172
172
  }
173
173
 
174
- // The top level is an implicit union, and the reference treats it as one for
175
- // this too: `square(4); cube(1);` warns and keeps only the square.
176
- void Evaluator::applyDimensionRulesTo(const std::vector<CSGNode*>& children, int ruleValue) {
174
+ // `keepMixed` warns about a mixed group but drops nothing -- the top level's
175
+ // behaviour, see generateTree.
176
+ void Evaluator::applyDimensionRulesTo(const std::vector<CSGNode*>& children, int ruleValue,
177
+ bool keepMixed) {
177
178
  const DimRule rule = static_cast<DimRule>(ruleValue);
178
179
  int target = rule == DimRule::Only2D ? 2 : rule == DimRule::Only3D ? 3 : 0;
179
180
  bool warnedMixing = false;
@@ -202,6 +203,16 @@ void Evaluator::applyDimensionRulesTo(const std::vector<CSGNode*>& children, int
202
203
  warn("Mixing 2D and 3D objects is not supported", pos);
203
204
  warnedMixing = true;
204
205
  }
206
+ if (keepMixed) {
207
+ // Kept, not dropped: at the top level there is no single
208
+ // result to build, so nothing downstream can be confused by
209
+ // the other dimension, and the reference's own preview shows
210
+ // both. The warning above still stands -- an export cannot
211
+ // represent the mix -- but silently losing the 2D geometry
212
+ // from the picture is worse than showing it.
213
+ kept.push_back(std::move(b));
214
+ continue;
215
+ }
205
216
  warn(std::string("Ignoring ") + (dim == 3 ? "3D" : "2D") + " child object for " +
206
217
  (target == 3 ? "3D" : "2D") + " operation",
207
218
  pos);
@@ -216,9 +227,19 @@ std::vector<ColoredBody> Evaluator::generateTree(const std::vector<std::unique_p
216
227
  ptrs.reserve(tree.size());
217
228
  for (const std::unique_ptr<CSGNode>& n : tree) ptrs.push_back(n.get());
218
229
  std::vector<ColoredBody> bodies = generateTreeImpl(ptrs);
219
- // The top level is an implicit union; give it the same Group treatment
220
- // every other group node gets, then re-collect what survived.
221
- applyDimensionRulesTo(ptrs, static_cast<int>(DimRule::Group));
230
+ // The top level is an implicit union, so it gets the same Group warning
231
+ // every other group node gets -- but it KEEPS both dimensions.
232
+ //
233
+ // A real group has to pick one: generateCsg builds a single result and
234
+ // switches on whether it holds a body or a section, so a group that
235
+ // changed dimension part-way used to take the process down. The top
236
+ // level builds no such result; it just hands back a list. Dropping there
237
+ // only lost geometry, and it lost it from the picture: the reference's
238
+ // preview draws top-level 2D and 3D side by side (a 2D shape as a thin
239
+ // slab), which is exactly what BOSL2's debug_bezier() of a 2D bezier
240
+ // relies on -- every curve and control arrow it draws is 2D, so beside
241
+ // a 3D sphere the whole debug overlay vanished.
242
+ applyDimensionRulesTo(ptrs, static_cast<int>(DimRule::Group), /*keepMixed=*/true);
222
243
  bodies.clear();
223
244
  for (CSGNode* n : ptrs) {
224
245
  for (const ColoredBody& b : n->bodies) bodies.push_back(b);
@@ -429,10 +429,23 @@ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& b
429
429
  std::vector<Solid> solids;
430
430
  std::vector<ExportObject> loose;
431
431
 
432
+ // A `flatPreview` body is a top-level 2D shape thin-extruded so it can be
433
+ // SEEN (toRenderableBodies). The top level keeps 2D and 3D side by side
434
+ // for exactly that reason -- but a mesh export of a mixed script must
435
+ // not smuggle a 1-unit-tall slab in beside the real solids, which is what
436
+ // the reference drops there too. A 2D-ONLY script still exports its
437
+ // slab, unchanged: that is the only geometry there is, and dropping it
438
+ // would leave nothing to write.
439
+ bool hasRealSolid = false;
440
+ for (const ColoredBody& cb : bodies) {
441
+ if (!cb.flatPreview && isExportable(cb)) { hasRealSolid = true; break; }
442
+ }
443
+
432
444
  int index = 0;
433
445
  for (const ColoredBody& cb : bodies) {
434
446
  ++index;
435
447
  if (!isExportable(cb)) continue;
448
+ if (cb.flatPreview && hasRealSolid) continue;
436
449
  if (cb.isDisplayOnly()) {
437
450
  // Manifold rejected this one -- an open shell is not a solid --
438
451
  // so it can join no boolean. Its triangles are real geometry the
@@ -1,4 +1,6 @@
1
1
  #include "openscad_cpp_evaluator/evaluator.hpp"
2
+ #include "openscad_cpp_evaluator/colored_body.hpp"
3
+ #include "openscad_cpp_evaluator/export.hpp"
2
4
 
3
5
  #include "test_helpers.hpp"
4
6
 
@@ -576,8 +578,11 @@ TEST(MixedDimensions, TransformsAndControlFlowAreGroupsToo) {
576
578
  EXPECT_EQ(dimWarnings("translate([0,0,0]) { square(4); cube(1); }"), expected);
577
579
  EXPECT_EQ(dimWarnings("scale(2) { square(4); cube(1); }"), expected);
578
580
  EXPECT_EQ(dimWarnings("color(\"red\") { square(4); cube(1); }"), expected);
579
- EXPECT_EQ(dimWarnings("for (i = [0:0]) { square(4); cube(1); }"), expected);
580
- EXPECT_EQ(dimWarnings("if (true) { square(4); cube(1); }"), expected);
581
+ // for/if are transparent -- their children land at the top level, so
582
+ // they get the top level's keep-both treatment rather than a group's.
583
+ const std::vector<std::string> topLevel{"WARNING: Mixing 2D and 3D objects is not supported"};
584
+ EXPECT_EQ(dimWarnings("for (i = [0:0]) { square(4); cube(1); }"), topLevel);
585
+ EXPECT_EQ(dimWarnings("if (true) { square(4); cube(1); }"), topLevel);
581
586
  EXPECT_EQ(dimWarnings("hull() { square(4); cube(1); }"), expected);
582
587
  }
583
588
 
@@ -603,14 +608,85 @@ TEST(MixedDimensions, RoofWarnsForNeither) {
603
608
  EXPECT_TRUE(dimWarnings("roof() { square(4); cube(1); }").empty());
604
609
  }
605
610
 
606
- TEST(MixedDimensions, TopLevelIsAGroupAsWell) {
607
- // The top level is an implicit union, and gets the same treatment.
611
+ TEST(MixedDimensions, TopLevelWarnsButKeepsBothDimensions) {
612
+ // The top level warns like any other group, but drops NOTHING. A real
613
+ // group has to pick one dimension because generateCsg builds a single
614
+ // result from it; the top level just hands back a list, and the
615
+ // reference's own preview draws 2D and 3D side by side. Dropping here
616
+ // silently lost the whole of a BOSL2 debug overlay (debug_bezier() of a
617
+ // 2D bezier is entirely 2D) whenever a 3D shape shared the script.
608
618
  EXPECT_EQ(dimWarnings("square(4); cube(1);"),
609
- (std::vector<std::string>{"WARNING: Ignoring 3D child object for 2D operation",
610
- "WARNING: Mixing 2D and 3D objects is not supported"}));
619
+ (std::vector<std::string>{"WARNING: Mixing 2D and 3D objects is not supported"}));
611
620
  Evaluated e = evalSrc("square(4); cube(1);");
621
+ ASSERT_EQ(e.bodies.size(), 2u);
622
+ EXPECT_TRUE(e.bodies[0].section.has_value()) << "the 2D shape survived";
623
+ EXPECT_TRUE(e.bodies[1].body.has_value()) << "and so did the 3D one";
624
+ }
625
+
626
+ TEST(MixedDimensions, ExportDropsTheFlatSlabWhenRealSolidsArePresent) {
627
+ // The top level keeps a 2D shape so it can be SEEN, thin-extruded by
628
+ // toRenderableBodies. A mesh export must not smuggle that 1-unit-tall slab
629
+ // in beside the real solids -- the reference drops it there too.
630
+ Evaluated e = evalSrc("square(4); cube(1);");
631
+ const std::vector<ColoredBody> renderable = toRenderableBodies(e.bodies);
632
+ ASSERT_EQ(renderable.size(), 2u) << "both are still drawn";
633
+
634
+ const std::vector<ExportObject> objects = splitBodiesForExport(renderable, nullptr);
635
+ ASSERT_EQ(objects.size(), 1u) << "but only the cube is exported";
636
+ // Only the cube survives, so the height is the cube's own.
637
+ float zmin = 1e9f, zmax = -1e9f;
638
+ for (size_t i = 2; i < objects[0].verts.size(); i += 3) {
639
+ zmin = std::min(zmin, objects[0].verts[i]);
640
+ zmax = std::max(zmax, objects[0].verts[i]);
641
+ }
642
+ EXPECT_NEAR(zmax - zmin, 1.0f, 1e-5f);
643
+ }
644
+
645
+ TEST(MixedDimensions, ExportKeepsTheFlatSlabWhenItIsTheOnlyGeometry) {
646
+ // A 2D-only script has nothing else to write, so its slab still exports
647
+ // -- unchanged behaviour, and the only reason the filter is conditional.
648
+ Evaluated e = evalSrc("square(4);");
649
+ const std::vector<ExportObject> objects =
650
+ splitBodiesForExport(toRenderableBodies(e.bodies), nullptr);
651
+ EXPECT_EQ(objects.size(), 1u);
652
+ }
653
+
654
+ TEST(MixedDimensions, ATranslatedTwoDeeShapeKeepsItsZ) {
655
+ // A CrossSection is purely 2D, so `translate([0,0,-1]) square(10)` had
656
+ // nowhere to put the -1 and dropped it. Two 2D shapes meant to be
657
+ // layered then came out exactly coplanar, z-fought, and whichever drew
658
+ // last hid the other -- BOSL2's isosurface contour() Example 1 renders a
659
+ // green contour over a blue backdrop pushed down by 1, and the backdrop
660
+ // covered the contour completely.
661
+ Evaluated e = evalSrc("translate([0,0,-1]) square(10, center=true);");
612
662
  ASSERT_EQ(e.bodies.size(), 1u);
613
- EXPECT_TRUE(e.bodies[0].section.has_value());
663
+ ASSERT_TRUE(e.bodies[0].section.has_value());
664
+ EXPECT_DOUBLE_EQ(e.bodies[0].sectionZ, -1.0);
665
+
666
+ const std::vector<ColoredBody> renderable = toRenderableBodies(e.bodies);
667
+ ASSERT_EQ(renderable.size(), 1u);
668
+ ASSERT_TRUE(renderable[0].body.has_value());
669
+ const manifold::Box box = renderable[0].body->BoundingBox();
670
+ EXPECT_NEAR(box.min.z, -1.0, 1e-9) << "the extruded slab sits where it was moved to";
671
+ }
672
+
673
+ TEST(MixedDimensions, TwoDeeZOffsetsAccumulateAndDefaultToZero) {
674
+ Evaluated flat = evalSrc("square(10);");
675
+ ASSERT_EQ(flat.bodies.size(), 1u);
676
+ EXPECT_DOUBLE_EQ(flat.bodies[0].sectionZ, 0.0);
677
+
678
+ Evaluated stacked = evalSrc(
679
+ "translate([0,0,2]) translate([0,0,3]) square(10);");
680
+ ASSERT_EQ(stacked.bodies.size(), 1u);
681
+ EXPECT_DOUBLE_EQ(stacked.bodies[0].sectionZ, 5.0);
682
+
683
+ // x/y still go into the CrossSection itself, not the offset.
684
+ Evaluated moved = evalSrc("translate([4,5,6]) square(10);");
685
+ ASSERT_EQ(moved.bodies.size(), 1u);
686
+ EXPECT_DOUBLE_EQ(moved.bodies[0].sectionZ, 6.0);
687
+ const manifold::Rect r = moved.bodies[0].section->Bounds();
688
+ EXPECT_NEAR(r.min.x, 4.0, 1e-9);
689
+ EXPECT_NEAR(r.min.y, 5.0, 1e-9);
614
690
  }
615
691
 
616
692
  TEST(MixedDimensions, UniformChildrenAreUntouched) {
@@ -91,7 +91,7 @@ TEST(ExportStl, NoGeometryThrows) {
91
91
  TEST(ToRenderableBodies, ThinExtrudesABareTopLevel2dShapeSoItCanExportAsStl) {
92
92
  // A bare `circle();` (no `body`, only `section`) would make writeStl
93
93
  // throw "No geometry to export" -- toRenderableBodies() converts it
94
- // into a thin (1e-3 unit tall) Manifold first, mirroring the reference
94
+ // into a 1-unit-tall Manifold first, mirroring the reference
95
95
  // CLI's own to_renderable_bodies() call right before export. Without
96
96
  // this step, no top-level 2D-only script could ever export a mesh.
97
97
  std::vector<ColoredBody> bodies = evalToBodies("circle(2, $fn=32);");
@@ -104,7 +104,9 @@ TEST(ToRenderableBodies, ThinExtrudesABareTopLevel2dShapeSoItCanExportAsStl) {
104
104
  ASSERT_TRUE(renderable[0].body.has_value());
105
105
  EXPECT_TRUE(renderable[0].flatPreview);
106
106
  manifold::Box bbox = renderable[0].body->BoundingBox();
107
- EXPECT_NEAR(bbox.max.z - bbox.min.z, 1e-3, 1e-9);
107
+ // 1 unit, matching what the reference extrudes a 2D preview to --
108
+ // measured against OpenSCAD at three camera tilts.
109
+ EXPECT_NEAR(bbox.max.z - bbox.min.z, 1.0, 1e-9);
108
110
 
109
111
  const std::string path = tempPath("flat_preview.stl").string();
110
112
  writeStl(path, renderable);
@@ -351,6 +351,43 @@ def test_parse_ast_raises_parse_error_on_bad_syntax():
351
351
  raise AssertionError("expected ParseError")
352
352
 
353
353
 
354
+ def test_format_source_round_trips_and_is_idempotent():
355
+ from openscad_cpp_evaluator import format_source
356
+
357
+ out = format_source("module thing(a=1,b=[1,2,3]){translate([0,0,1])cube([a,a,a]);}\n")
358
+ assert "module thing(a=1, b=[1, 2, 3])" in out, out
359
+ # A modifier's children go on their own indented line.
360
+ assert "translate([0, 0, 1])\n cube([a, a, a]);" in out, out
361
+ # Formatting formatted source changes nothing: the printer is the
362
+ # parser's own, so its output parses back to the same tree.
363
+ assert format_source(out) == out
364
+
365
+
366
+ def test_format_source_keeps_comments():
367
+ from openscad_cpp_evaluator import format_source
368
+
369
+ out = format_source("// keep me\ncube(1);\n")
370
+ assert "// keep me" in out, out
371
+ # ...and can be told not to.
372
+ assert "// keep me" not in format_source("// keep me\ncube(1);\n", include_comments=False)
373
+
374
+
375
+ def test_format_source_honours_the_indent_width():
376
+ from openscad_cpp_evaluator import format_source
377
+
378
+ two = format_source("module m(){cube(1);}\n", indent=2)
379
+ assert "\n cube(1);" in two, two
380
+
381
+
382
+ def test_format_source_raises_parse_error_on_bad_input():
383
+ from openscad_cpp_evaluator import ParseError, format_source
384
+ try:
385
+ format_source("module {{{")
386
+ except ParseError:
387
+ return
388
+ raise AssertionError("expected ParseError")
389
+
390
+
354
391
  def main():
355
392
  tests = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
356
393
  failures = []