openscad-cpp-evaluator 0.55.1__tar.gz → 0.55.2__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.2}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/bindings/ast_to_py.cpp +8 -0
  3. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/bindings/module.cpp +8 -0
  4. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/evaluator.hpp +2 -1
  5. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/python/openscad_cpp_evaluator/__init__.py +20 -1
  7. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/csg_generate.cpp +27 -6
  8. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/export.cpp +13 -0
  9. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_booleans.cpp +46 -8
  10. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_python_bindings.py +37 -0
  11. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/.github/workflows/ci.yml +0 -0
  12. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/.github/workflows/wheels.yml +0 -0
  13. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/.gitignore +0 -0
  14. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/.gitmodules +0 -0
  15. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/CLAUDE.md +0 -0
  16. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/CMakeLists.txt +0 -0
  17. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/LICENSE +0 -0
  18. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/README.md +0 -0
  19. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/bindings/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/cmake/embed_font.cmake +0 -0
  21. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/examples/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/examples/eval_perf_benchmark.cpp +0 -0
  23. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/examples/manifold_cache_reuse.cpp +0 -0
  24. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/examples/minimal_debugger.cpp +0 -0
  25. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  26. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/.gitignore +0 -0
  27. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  28. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/README.md +0 -0
  30. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  31. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  32. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  33. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  34. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  35. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  36. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  37. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  38. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  39. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  41. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  42. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  43. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  44. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  45. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  46. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/api.cpp +0 -0
  47. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  48. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  49. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  50. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  51. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  52. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  53. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  54. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  55. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  56. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  57. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  58. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  59. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  60. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  61. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  62. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  63. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/position.cpp +0 -0
  64. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  65. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  66. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  67. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  68. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  69. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  70. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  71. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  72. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  73. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  74. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  75. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  76. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  77. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  78. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  79. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  80. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  81. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  82. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  83. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  84. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  85. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  86. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  87. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  88. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  89. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  90. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  91. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  92. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  93. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  94. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  95. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  96. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  97. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  98. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  99. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  100. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  101. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  102. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  103. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  104. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  105. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  106. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  107. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/export.hpp +0 -0
  108. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  109. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  110. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  111. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  112. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  113. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  114. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  115. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  116. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  117. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  118. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  119. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  120. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  121. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  122. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/value.hpp +0 -0
  123. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  124. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/resources/fonts/LICENSE +0 -0
  125. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  126. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/CMakeLists.txt +0 -0
  127. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/booleans.cpp +0 -0
  128. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/builtins.hpp +0 -0
  129. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/call_args.cpp +0 -0
  130. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/color.cpp +0 -0
  131. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/control.cpp +0 -0
  132. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/dxf_dim.cpp +0 -0
  133. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/extrude.cpp +0 -0
  134. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/function_builtins.cpp +0 -0
  135. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/import.cpp +0 -0
  136. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/modifiers.cpp +0 -0
  137. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/primitives_2d.cpp +0 -0
  138. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/primitives_3d.cpp +0 -0
  139. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/registry.cpp +0 -0
  140. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/roof.cpp +0 -0
  141. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/segments.cpp +0 -0
  142. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/surface.cpp +0 -0
  143. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/text.cpp +0 -0
  144. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/topology.cpp +0 -0
  145. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/builtins/transforms.cpp +0 -0
  146. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/bytecode_compiler.cpp +0 -0
  147. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/bytecode_vm.cpp +0 -0
  148. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/colored_body.cpp +0 -0
  149. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/csg_resolve.cpp +0 -0
  150. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/css_colors.cpp +0 -0
  151. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/debug/debug_repl.cpp +0 -0
  152. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/debug_profile.cpp +0 -0
  153. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/eval_context.cpp +0 -0
  154. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/eval_error.cpp +0 -0
  155. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/eval_use.cpp +0 -0
  156. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/expr_eval.cpp +0 -0
  157. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/import/dxf_import.cpp +0 -0
  158. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/import/mesh_import.cpp +0 -0
  159. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/import/surface_load.cpp +0 -0
  160. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/import/svg_import.cpp +0 -0
  161. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/manifold_cache.cpp +0 -0
  162. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/measure_geometry.cpp +0 -0
  163. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/mesh_check.cpp +0 -0
  164. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/stmt_eval.cpp +0 -0
  165. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/text/font_match.cpp +0 -0
  166. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/text/freetype_font_provider.cpp +0 -0
  167. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/text/text_metrics.cpp +0 -0
  168. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/user_calls.cpp +0 -0
  169. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/value.cpp +0 -0
  170. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/src/zip_stored.cpp +0 -0
  171. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/CMakeLists.txt +0 -0
  172. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_bytecode_compiler.cpp +0 -0
  173. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_children_separate.cpp +0 -0
  174. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_cli.cpp +0 -0
  175. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_control_flow.cpp +0 -0
  176. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_csg_tree.cpp +0 -0
  177. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_debug_hooks.cpp +0 -0
  178. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_dxf_svg_import.cpp +0 -0
  179. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_eval_context.cpp +0 -0
  180. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_export.cpp +0 -0
  181. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_expr_eval.cpp +0 -0
  182. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_extrude_roof.cpp +0 -0
  183. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_feature_detection.cpp +0 -0
  184. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_function_builtins.cpp +0 -0
  185. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_helpers.hpp +0 -0
  186. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_import_export.cpp +0 -0
  187. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_manifold_cache.cpp +0 -0
  188. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_mesh_check.cpp +0 -0
  189. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_multi_color_merge.cpp +0 -0
  190. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_primitives.cpp +0 -0
  191. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_profiling.cpp +0 -0
  192. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_render_expr.cpp +0 -0
  193. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_scope_trail.cpp +0 -0
  194. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_scoping.cpp +0 -0
  195. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_surface.cpp +0 -0
  196. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_tail_calls.cpp +0 -0
  197. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_text.cpp +0 -0
  198. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_transforms.cpp +0 -0
  199. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_unexpected_args.cpp +0 -0
  200. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_use.cpp +0 -0
  201. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_value.cpp +0 -0
  202. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_viewport_params.cpp +0 -0
  203. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tests/test_zip_stored.cpp +0 -0
  204. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tools/cli/CMakeLists.txt +0 -0
  205. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tools/cli/cli_lib.cpp +0 -0
  206. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tools/cli/cli_lib.hpp +0 -0
  207. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/tools/cli/main.cpp +0 -0
  208. {openscad_cpp_evaluator-0.55.1 → openscad_cpp_evaluator-0.55.2}/uv.lock +0 -0
@@ -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.2
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,
@@ -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.2"
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")
@@ -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 1e-3-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,47 @@ 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);");
612
- ASSERT_EQ(e.bodies.size(), 1u);
613
- EXPECT_TRUE(e.bodies[0].section.has_value());
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 1e-3-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
+ // The cube is a unit tall; the dropped slab would have been 1e-3.
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);
614
652
  }
615
653
 
616
654
  TEST(MixedDimensions, UniformChildrenAreUntouched) {
@@ -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 = []