openscad-cpp-evaluator 1.25.0__tar.gz → 1.27.0__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 (221) hide show
  1. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/CLAUDE.md +21 -1
  2. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/bindings/module.cpp +16 -6
  4. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/colored_body.hpp +17 -1
  5. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/evaluator.hpp +3 -0
  6. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/pyproject.toml +1 -1
  7. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/python/openscad_cpp_evaluator/__init__.py +11 -3
  8. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/colored_body.cpp +16 -6
  9. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/csg_generate.cpp +24 -0
  10. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_python_bindings.py +75 -0
  11. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/.github/workflows/ci.yml +0 -0
  12. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/.github/workflows/wheels.yml +0 -0
  13. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/.gitignore +0 -0
  14. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/.gitmodules +0 -0
  15. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/CMakeLists.txt +0 -0
  16. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/LICENSE +0 -0
  17. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/README.md +0 -0
  18. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/bindings/CMakeLists.txt +0 -0
  19. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/bindings/ast_to_py.cpp +0 -0
  20. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/cmake/embed_font.cmake +0 -0
  21. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/examples/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/examples/eval_perf_benchmark.cpp +0 -0
  23. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/examples/manifold_cache_reuse.cpp +0 -0
  24. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/examples/minimal_debugger.cpp +0 -0
  25. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  26. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/.gitignore +0 -0
  27. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  28. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/README.md +0 -0
  30. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  31. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  32. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  33. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  34. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  35. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  36. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  37. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  38. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  39. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  41. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  42. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  43. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  44. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  45. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  46. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  47. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  48. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  49. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  50. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  51. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  52. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  53. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  54. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  55. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  56. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  57. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  58. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  59. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  60. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  61. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  62. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  63. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  64. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  65. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  66. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  67. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  68. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  69. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  70. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  71. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  72. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  73. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  74. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  75. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  76. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  77. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  78. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  79. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  80. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  81. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  82. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  83. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  84. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  85. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  86. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  87. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  88. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  89. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  90. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  91. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  92. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  93. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  94. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  95. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  96. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  97. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  98. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  99. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  100. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  101. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  102. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  103. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  104. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  105. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  106. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  107. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  108. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  109. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  110. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  111. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  112. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  113. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  114. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  115. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  116. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  117. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  118. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  119. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  120. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  121. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  122. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  123. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  124. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  125. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  126. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  127. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  128. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/resources/fonts/LICENSE +0 -0
  129. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  130. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  131. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  132. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  133. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/CMakeLists.txt +0 -0
  134. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/booleans.cpp +0 -0
  135. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/builtins.hpp +0 -0
  136. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/call_args.cpp +0 -0
  137. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/color.cpp +0 -0
  138. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/control.cpp +0 -0
  139. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/dxf_dim.cpp +0 -0
  140. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/extrude.cpp +0 -0
  141. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/function_builtins.cpp +0 -0
  142. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/import.cpp +0 -0
  143. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/modifiers.cpp +0 -0
  144. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/primitives_2d.cpp +0 -0
  145. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/primitives_3d.cpp +0 -0
  146. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/registry.cpp +0 -0
  147. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/roof.cpp +0 -0
  148. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/segments.cpp +0 -0
  149. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/surface.cpp +0 -0
  150. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/text.cpp +0 -0
  151. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/topology.cpp +0 -0
  152. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/builtins/transforms.cpp +0 -0
  153. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/bytecode_compiler.cpp +0 -0
  154. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/bytecode_vm.cpp +0 -0
  155. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/coverage.cpp +0 -0
  156. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/csg_resolve.cpp +0 -0
  157. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/css_colors.cpp +0 -0
  158. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/debug/debug_repl.cpp +0 -0
  159. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/debug_profile.cpp +0 -0
  160. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/eval_context.cpp +0 -0
  161. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/eval_error.cpp +0 -0
  162. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/eval_use.cpp +0 -0
  163. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/export.cpp +0 -0
  164. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/expr_eval.cpp +0 -0
  165. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/format_closure.cpp +0 -0
  166. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/import/dxf_import.cpp +0 -0
  167. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/import/mesh_import.cpp +0 -0
  168. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/import/surface_load.cpp +0 -0
  169. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/import/svg_import.cpp +0 -0
  170. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/manifold_cache.cpp +0 -0
  171. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/measure_geometry.cpp +0 -0
  172. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/mesh_check.cpp +0 -0
  173. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/stmt_eval.cpp +0 -0
  174. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/text/font_match.cpp +0 -0
  175. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/text/freetype_font_provider.cpp +0 -0
  176. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/text/text_metrics.cpp +0 -0
  177. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/user_calls.cpp +0 -0
  178. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/utf8.cpp +0 -0
  179. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/value.cpp +0 -0
  180. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/src/zip_stored.cpp +0 -0
  181. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/CMakeLists.txt +0 -0
  182. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_booleans.cpp +0 -0
  183. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_bytecode_compiler.cpp +0 -0
  184. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_children_separate.cpp +0 -0
  185. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_cli.cpp +0 -0
  186. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_control_flow.cpp +0 -0
  187. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_coverage.cpp +0 -0
  188. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_csg_tree.cpp +0 -0
  189. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_debug_hooks.cpp +0 -0
  190. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_dxf_svg_import.cpp +0 -0
  191. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_eval_context.cpp +0 -0
  192. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_export.cpp +0 -0
  193. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_expr_eval.cpp +0 -0
  194. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_extrude_roof.cpp +0 -0
  195. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_feature_detection.cpp +0 -0
  196. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_function_builtins.cpp +0 -0
  197. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_helpers.hpp +0 -0
  198. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_import_export.cpp +0 -0
  199. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_include_cache.cpp +0 -0
  200. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_manifold_cache.cpp +0 -0
  201. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_mesh_check.cpp +0 -0
  202. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_multi_color_merge.cpp +0 -0
  203. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_primitives.cpp +0 -0
  204. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_profiling.cpp +0 -0
  205. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_render_expr.cpp +0 -0
  206. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_scope_trail.cpp +0 -0
  207. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_scoping.cpp +0 -0
  208. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_surface.cpp +0 -0
  209. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_tail_calls.cpp +0 -0
  210. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_text.cpp +0 -0
  211. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_transforms.cpp +0 -0
  212. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_unexpected_args.cpp +0 -0
  213. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_use.cpp +0 -0
  214. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_value.cpp +0 -0
  215. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_viewport_params.cpp +0 -0
  216. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tests/test_zip_stored.cpp +0 -0
  217. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tools/cli/CMakeLists.txt +0 -0
  218. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tools/cli/cli_lib.cpp +0 -0
  219. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tools/cli/cli_lib.hpp +0 -0
  220. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/tools/cli/main.cpp +0 -0
  221. {openscad_cpp_evaluator-1.25.0 → openscad_cpp_evaluator-1.27.0}/uv.lock +0 -0
@@ -1135,6 +1135,12 @@ grep for `ponytail:`.
1135
1135
  `writeSvg`, which needs the sections that step throws away, and which says so rather than writing
1136
1136
  the slab's outline. `tools/cli/cli_lib.cpp` is the only current caller; a future GUI/renderer
1137
1137
  caller needs the same call before passing results to its own mesh consumer.
1138
+ **The slab height is a parameter** (`flatHeight`, default `kTopLevel2dHeight` = 1.0, the
1139
+ reference's measured preview height that docs images and `--viewall` framing depend on). Python
1140
+ reaches it as `Evaluator(flat_preview_height=...)`, which thins only the DISPLAYED bodies:
1141
+ `evaluate()` converts the raw list a second time at 1.0 for `geometry`, because a 2D-only script
1142
+ exports its slab and that must not become whatever a viewer found easiest to look at. BelfrySCAD's
1143
+ viewport asks for 0.01 so a 2D shape reads as the flat contour it is.
1138
1144
  - `include/openscad_cpp_evaluator/function_builtins.hpp`, `src/builtins/function_builtins.cpp` —
1139
1145
  the math/string/list/type-check builtin *function* dispatch (`isBuiltinFunctionName`/
1140
1146
  `evalBuiltinFunction`), a completely separate namespace/table from `dispatch.hpp`'s builtin
@@ -1687,6 +1693,20 @@ the former is worth dragging.
1687
1693
  A cache hit restamps to the chain reaching the geometry *now*, not the one that
1688
1694
  first produced it: a module called twice genuinely is two call chains.
1689
1695
 
1696
+ **A 2D shape is picked by `ColoredBody::sectionId`.** A `CrossSection` is contours
1697
+ with no run IDs, so a top-level 2D shape had no `idToNode` entry at all: its preview
1698
+ slab got fresh IDs from `Manifold::Extrude` in `toRenderableBodies`, after the table
1699
+ was built, and a click on it mapped to no source (it blocked BelfrySCAD's extrude
1700
+ gizmo, which exists to act on exactly those). Now the generate loop calls
1701
+ `tagSections` after every builtin's `GenerateFn`: any 2D body without an ID gets one
1702
+ reserved from Manifold's counter, attributed to that node. A transform keeps the ID
1703
+ it was given (it edits the body in place), so a 2D shape stays its primitive, as a
1704
+ solid's runs do; an op that builds a *new* section -- a 2D `union()`, `offset()`,
1705
+ `hull()` -- claims it, so the whole union is one pick. `restampCachedIds` restamps it
1706
+ by the solid rule (across renders, to the reused node), `recordRunColors` records its
1707
+ colour, and `toRenderableBodies` rebuilds the slab from a `MeshGL` whose one run
1708
+ carries it. Tested in `tests/test_python_bindings.py` (`_picked_text`).
1709
+
1690
1710
  No stored script path is needed to tell the user's file from a library:
1691
1711
  `callStack_.front()` is by construction the call made from top level.
1692
1712
 
@@ -1695,7 +1715,7 @@ Exposed to Python as `node.call_sites` (innermost-first `_CallFrame`s, each a
1695
1715
 
1696
1716
  `Evaluator::measuring_` is set for the whole generate. It suppresses the four
1697
1717
  writes that exist solely to describe *drawn* geometry — `idToNode`/`idToColor`/`idToCallChain` in
1698
- `tagGenerated` and `tagDisplayOnly`, the `restampCachedIds` call on a cache hit,
1718
+ `tagGenerated`, `tagDisplayOnly` and `tagSections`, the `restampCachedIds` call on a cache hit,
1699
1719
  and `cacheProducer_` — because those tables are cleared once per pass, so a leak
1700
1720
  is permanent and surfaces later as wrong click-to-source. It also suppresses
1701
1721
  `checkDebug`, which would otherwise inject stops at the paused statement's own
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.25.0
3
+ Version: 1.27.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -505,10 +505,15 @@ nb::object coverageResultToPy(const std::optional<oscadeval::CoverageResult>& cr
505
505
 
506
506
  nb::object evaluate(const std::string& path, nb::dict viewportParams,
507
507
  std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, bool profile,
508
- bool generate, bool strictCommas, bool coverage, bool keepMinuendColor) {
508
+ bool generate, bool strictCommas, bool coverage, bool keepMinuendColor,
509
+ double flatPreviewHeight) {
509
510
  std::unordered_map<std::string, oscadeval::Value> vp = toViewportParams(viewportParams);
510
511
 
511
512
  std::vector<oscadeval::ColoredBody> bodies;
513
+ // What export gets. The same list as `bodies` unless the caller asked
514
+ // for a thinner preview slab: a 2D-only script exports its slab, and
515
+ // that must stay the reference's 1 unit whatever the viewer shows.
516
+ std::vector<oscadeval::ColoredBody> exportBodies;
512
517
  std::vector<std::string> echoes;
513
518
  std::vector<IdSpan> idSpans;
514
519
  std::vector<std::unique_ptr<oscadeval::CSGNode>> csgTree;
@@ -532,7 +537,10 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
532
537
  ev.keepMinuendColor = keepMinuendColor;
533
538
  ev.setUsedFileGlobals(used.usedFileGlobals);
534
539
  oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
535
- bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp, generate));
540
+ std::vector<oscadeval::ColoredBody> raw = ev.evaluate(used.processedNodes, ctx, vp, generate);
541
+ bodies = oscadeval::toRenderableBodies(raw, flatPreviewHeight);
542
+ exportBodies = flatPreviewHeight == oscadeval::kTopLevel2dHeight
543
+ ? bodies : oscadeval::toRenderableBodies(raw);
536
544
  collectIdSpans(ev, idSpans);
537
545
  // Only moved out when it will actually be converted below --
538
546
  // csgTreeToPy() walks the whole tree building Python objects,
@@ -567,8 +575,8 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
567
575
  nb::list echoList;
568
576
  for (const std::string& s : echoes) echoList.append(s);
569
577
  auto geom = std::make_shared<Geometry>();
570
- geom->bodies = std::move(bodies);
571
- return nb::make_tuple(bodiesToList(geom->bodies), echoList, idSpansToDict(idSpans), csgTreeToPy(csgTree),
578
+ geom->bodies = std::move(exportBodies);
579
+ return nb::make_tuple(bodiesToList(bodies), echoList, idSpansToDict(idSpans), csgTreeToPy(csgTree),
572
580
  profileResultToPy(profileResult), dyn, dynExplicit, geom, coverageResultToPy(coverageResult));
573
581
  }
574
582
 
@@ -780,7 +788,7 @@ void callPyReturnHook(nb::handle returnHook, const std::string& name, const osca
780
788
  nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::callable debugHook,
781
789
  nb::callable errorBreak, nb::callable echoFn,
782
790
  std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, nb::object returnHook,
783
- FastContinueSignal* fastContinueSignal) {
791
+ FastContinueSignal* fastContinueSignal, double flatPreviewHeight) {
784
792
  std::unordered_map<std::string, oscadeval::Value> vp = toViewportParams(viewportParams);
785
793
 
786
794
  std::vector<oscadeval::ColoredBody> bodies;
@@ -841,7 +849,7 @@ nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::c
841
849
  if (fastContinueSignal) ev.setFastContinueInterruptFlag(fastContinueSignal->flag());
842
850
  ev.setUsedFileGlobals(used.usedFileGlobals);
843
851
  oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
844
- bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp));
852
+ bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp), flatPreviewHeight);
845
853
  collectIdSpans(ev, idSpans);
846
854
  {
847
855
  nb::gil_scoped_acquire g;
@@ -985,6 +993,7 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
985
993
  m.def("evaluate", &evaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("manifold_cache") = nullptr,
986
994
  nb::arg("profile") = false, nb::arg("generate") = true, nb::arg("strict_commas") = false,
987
995
  nb::arg("coverage") = false, nb::arg("keep_minuend_color") = false,
996
+ nb::arg("flat_preview_height") = oscadeval::kTopLevel2dHeight,
988
997
  "Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit, "
989
998
  "geometry, coverage_result).\n"
990
999
  "coverage=True records which statements, branch arms and bodies ran: coverage_result is a dict "
@@ -1024,5 +1033,6 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
1024
1033
  m.def("debug_evaluate", &debugEvaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("debug_hook"),
1025
1034
  nb::arg("error_break"), nb::arg("echo_fn"), nb::arg("manifold_cache") = nullptr,
1026
1035
  nb::arg("return_hook") = nb::none(), nb::arg("fast_continue_signal") = nullptr,
1036
+ nb::arg("flat_preview_height") = oscadeval::kTopLevel2dHeight,
1027
1037
  "Evaluate with the debugger wired in; returns (bodies, [], id_to_node, dyn, dyn_explicit). Callbacks fire under the GIL.");
1028
1038
  }
@@ -42,6 +42,15 @@ struct ColoredBody {
42
42
  std::optional<manifold::CrossSection> section;
43
43
  bool flatPreview = false;
44
44
 
45
+ // The originalID a 2D `section` is picked by. A CrossSection is contours
46
+ // and carries no run IDs, so a 2D shape had no entry in idToNode and a
47
+ // click on its preview slab mapped to no source at all. Reserved from
48
+ // Manifold's own counter (so it cannot collide with a solid's ID) by the
49
+ // generate loop, for the node that built the section -- a primitive, or
50
+ // an op like union() that made a new one; a transform keeps the one it
51
+ // was given. toRenderableBodies() gives the slab this ID.
52
+ std::optional<uint32_t> sectionId;
53
+
45
54
  // What a 2D `section` has been transformed BY that a CrossSection
46
55
  // cannot itself hold: a translation along Z, a rotation out of the XY
47
56
  // plane, anything with a Z component. Applied when
@@ -160,6 +169,13 @@ std::optional<std::array<float, 4>> valueToColor(const Value& v);
160
169
  // (the CLI, any other mesh-export caller) should apply this to
161
170
  // Evaluator::evaluate()'s result before exporting, the same way the
162
171
  // reference's own cli.py does right before export_bodies().
163
- std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodies);
172
+ // The thickness toRenderableBodies() gives a top-level 2D shape's slab by
173
+ // default: OpenSCAD's own preview height, measured (see colored_body.cpp).
174
+ // Docs images and exports use it; an interactive viewer may pass a thinner
175
+ // one so a 2D shape reads as the flat contour it is.
176
+ inline constexpr double kTopLevel2dHeight = 1.0;
177
+
178
+ std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodies,
179
+ double flatHeight = kTopLevel2dHeight);
164
180
 
165
181
  } // namespace oscadeval
@@ -283,6 +283,9 @@ public:
283
283
  // attributes there).
284
284
  // Give cached bodies fresh originalIDs so a second call site reusing
285
285
  // them is not confused with the first. See its definition.
286
+ // Give every 2D body in `bodies` that has no sectionId yet one of its
287
+ // own, attributed to `node`. See ColoredBody::sectionId.
288
+ void tagSections(std::vector<ColoredBody>& bodies, const oscad::ASTNode& node, uint32_t callChain);
286
289
  void restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::ASTNode& node,
287
290
  const oscad::ASTNode* producer, uint32_t callChain);
288
291
 
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.25.0"
7
+ version = "1.27.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -565,7 +565,7 @@ class Evaluator:
565
565
 
566
566
  def __init__(self, echo_fn=None, debug_hook=None, error_break_fn=None, return_hook=None,
567
567
  manifold_cache=None, profile=False, fast_continue_signal=None, coverage=False,
568
- keep_minuend_color=False):
568
+ keep_minuend_color=False, flat_preview_height=1.0):
569
569
  self._echo_fn = echo_fn
570
570
  self._debug_hook = debug_hook
571
571
  self._error_break_fn = error_break_fn
@@ -579,6 +579,12 @@ class Evaluator:
579
579
  # subtrahend's (or the cut green). A viewer option; see
580
580
  # Evaluator::keepMinuendColor in evaluator.hpp.
581
581
  self._keep_minuend_color = keep_minuend_color
582
+ # How thick a top-level 2D shape's preview slab is in the returned
583
+ # bodies. 1.0 is OpenSCAD's preview height, which docs images and
584
+ # --viewall framing depend on; a viewer can pass something thin so
585
+ # a 2D shape reads as a flat contour. Export (`geometry`) always
586
+ # keeps 1.0: a 2D-only script exports its slab.
587
+ self._flat_preview_height = float(flat_preview_height)
582
588
  self.csg_tree = []
583
589
  self.profile_result = None
584
590
  # coverage=True: after evaluate(), {"spans": [...], "files": [...],
@@ -621,7 +627,8 @@ class Evaluator:
621
627
  source_path, vp, self._debug_hook,
622
628
  self._error_break_fn or (lambda *a, **k: None),
623
629
  self._echo_fn or (lambda _m: None),
624
- self._manifold_cache, self._return_hook, self._fast_continue_signal)
630
+ self._manifold_cache, self._return_hook, self._fast_continue_signal,
631
+ self._flat_preview_height)
625
632
  self.csg_tree = []
626
633
  self.profile_result = None
627
634
  # The debugger path has no geometry handle: debug_evaluate
@@ -632,7 +639,8 @@ class Evaluator:
632
639
  (body_dicts, echoes, id_spans, csg_tree, profile_result, dyn,
633
640
  dyn_explicit, geometry, coverage_result) = _ext.evaluate(
634
641
  source_path, vp, self._manifold_cache, self._profile, generate,
635
- strict_commas, self._coverage, self._keep_minuend_color)
642
+ strict_commas, self._coverage, self._keep_minuend_color,
643
+ self._flat_preview_height)
636
644
  # The evaluated bodies, still on the C++ side. Stashed like
637
645
  # csg_tree/profile_result rather than returned, so
638
646
  # evaluate()'s own 2-tuple result is unchanged -- callers
@@ -20,23 +20,33 @@ std::optional<std::array<float, 4>> valueToColor(const Value& v) {
20
20
  return c;
21
21
  }
22
22
 
23
- namespace {
24
- // The reference extrudes a 2D shape to exactly 1 unit for preview -- measured
23
+ // kTopLevel2dHeight (colored_body.hpp), the default slab height: the
24
+ // reference extrudes a 2D shape to exactly 1 unit for preview -- measured
25
25
  // against OpenSCAD at three camera tilts (60/70/80 degrees), which put it at
26
26
  // 0.92/1.00/0.99, the 60 being pixel-rounding on the shallowest angle. Ours
27
27
  // was 1e-3, which reads as a flat silhouette with no visible edge and, more
28
28
  // annoyingly, gives --viewall a different bounding box to fit, so every 2D
29
29
  // docs image came out at a different scale from the published one.
30
- constexpr double kTopLevel2dHeight = 1.0;
31
- } // namespace
32
30
 
33
- std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodies) {
31
+ std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodies, double flatHeight) {
34
32
  std::vector<ColoredBody> out;
35
33
  out.reserve(bodies.size());
36
34
  for (const ColoredBody& cb : bodies) {
37
35
  if (!cb.body && cb.section) {
38
36
  ColoredBody flat;
39
- flat.body = manifold::Manifold::Extrude(cb.section->ToPolygons(), kTopLevel2dHeight);
37
+ flat.body = manifold::Manifold::Extrude(cb.section->ToPolygons(), flatHeight);
38
+ if (cb.sectionId && !flat.body->IsEmpty()) {
39
+ // One run carrying the section's own ID, so a click on the
40
+ // slab finds the node that built the shape (see
41
+ // ColoredBody::sectionId). Manifold keeps a MeshGL's
42
+ // runOriginalID when it is given one.
43
+ manifold::MeshGL mesh = flat.body->GetMeshGL();
44
+ mesh.runOriginalID = {*cb.sectionId};
45
+ mesh.runIndex = {0, static_cast<uint32_t>(mesh.triVerts.size())};
46
+ mesh.runTransform.clear();
47
+ mesh.faceID.clear();
48
+ flat.body = manifold::Manifold(mesh);
49
+ }
40
50
  // Put it back where the transforms a CrossSection could not
41
51
  // hold would have placed it -- a Z offset, a rotation out of
42
52
  // the plane. See ColoredBody::sectionXform.
@@ -124,6 +124,7 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
124
124
  generateWarnEntry = node.warnEntry;
125
125
  generateCallChain = node.callChain;
126
126
  node.bodies = it->second(*this, node.params, node.children, *node.node);
127
+ if (!measuring_) tagSections(node.bodies, *node.node, node.callChain);
127
128
  generateWarnEntry = savedWarnEntry;
128
129
  generateCallChain = savedCallChain;
129
130
  } else {
@@ -393,6 +394,16 @@ void Evaluator::restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::
393
394
  // its producer check keeps the first copy's descendants. They occupy
394
395
  // the same space, so there is nothing to select apart.
395
396
  for (ColoredBody& cb : bodies) {
397
+ // A 2D body's one ID, restamped by the same rule as a solid's runs.
398
+ if (cb.sectionId) {
399
+ const uint32_t fresh = manifold::Manifold::ReserveIDs(1);
400
+ auto old = idToNode.find(*cb.sectionId);
401
+ idToNode[fresh] =
402
+ (old != idToNode.end() && old->second != producer) ? old->second : &node;
403
+ idToCallChain[fresh] = callChain;
404
+ idToColor[fresh] = cb.color;
405
+ cb.sectionId = fresh;
406
+ }
396
407
  if (!cb.body || cb.body->IsEmpty()) continue;
397
408
  manifold::MeshGL mesh = cb.body->GetMeshGL();
398
409
  if (mesh.runOriginalID.empty()) continue;
@@ -475,6 +486,7 @@ void Evaluator::restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::
475
486
 
476
487
 
477
488
  void Evaluator::recordRunColors(ColoredBody& b, const std::optional<std::array<float, 4>>& rgba) {
489
+ if (b.sectionId) idToColor[*b.sectionId] = rgba;
478
490
  if (!b.body || bodyIsEmpty(b)) return;
479
491
  // A body that is still one original knows its own ID without building
480
492
  // a mesh -- the common case, and the cheap one.
@@ -486,6 +498,18 @@ void Evaluator::recordRunColors(ColoredBody& b, const std::optional<std::array<f
486
498
  for (uint32_t id : b.body->GetMeshGL().runOriginalID) idToColor[id] = rgba;
487
499
  }
488
500
 
501
+ void Evaluator::tagSections(std::vector<ColoredBody>& bodies, const oscad::ASTNode& node,
502
+ uint32_t callChain) {
503
+ for (ColoredBody& cb : bodies) {
504
+ if (!cb.section || cb.body || cb.sectionId) continue;
505
+ const uint32_t id = manifold::Manifold::ReserveIDs(1);
506
+ idToNode[id] = &node;
507
+ idToColor[id] = cb.color;
508
+ idToCallChain[id] = callChain;
509
+ cb.sectionId = id;
510
+ }
511
+ }
512
+
489
513
  ColoredBody Evaluator::tagGenerated(manifold::Manifold body, const oscad::ASTNode& node, const Value& colorValue) {
490
514
  manifold::MeshGL mesh = body.GetMeshGL();
491
515
  std::optional<std::array<float, 4>> color = valueToColor(colorValue);
@@ -1046,3 +1046,78 @@ def test_svg_class_is_feature_detectable(tmp_path):
1046
1046
  out = []
1047
1047
  Evaluator(echo_fn=out.append).evaluate(str(scad), generate=False)
1048
1048
  assert "1" in out[0], out
1049
+
1050
+
1051
+ def _picked_text(tmp_path, src, cache=None, name="s.scad"):
1052
+ """What a click on each body would select: the source text its run IDs
1053
+ map to, per body, in body order."""
1054
+ script = tmp_path / name
1055
+ script.write_text(src)
1056
+ ev = Evaluator(manifold_cache=cache) if cache else Evaluator()
1057
+ bodies, id_to_node = ev.evaluate(str(script), {})
1058
+ out = []
1059
+ for b in bodies:
1060
+ ids = [int(i) for i in b.body.to_mesh().run_original_id]
1061
+ out.append([src[id_to_node[i].position.start_offset:id_to_node[i].position.end_offset]
1062
+ if i in id_to_node else None for i in ids])
1063
+ return out
1064
+
1065
+
1066
+ def test_a_2d_shape_can_be_picked_back_to_its_source(tmp_path):
1067
+ """A CrossSection carries no run IDs, so a 2D shape's preview slab used
1068
+ to map to no node at all and could not be selected (BelfrySCAD's
1069
+ extrude gizmo needs exactly that)."""
1070
+ picked = _picked_text(tmp_path, "square(3);\ncircle(4);\n")
1071
+ assert picked == [["square(3);"], ["circle(4);"]]
1072
+
1073
+
1074
+ def test_a_transform_keeps_the_2d_shape_it_moved(tmp_path):
1075
+ picked = _picked_text(tmp_path, "translate([10, 0]) circle(4);\n")
1076
+ assert len(picked) == 1 and picked[0][0] is not None
1077
+ assert picked[0][0].startswith("circle(4)") or picked[0][0].startswith("translate")
1078
+
1079
+
1080
+ def test_a_2d_union_is_its_own_shape(tmp_path):
1081
+ src = "union() { square(3); circle(4); }\n"
1082
+ assert _picked_text(tmp_path, src) == [[src.strip()]]
1083
+
1084
+
1085
+ def test_a_cached_2d_shape_is_picked_as_a_solid_would_be(tmp_path):
1086
+ """A cache hit restamps IDs; a 2D shape's must follow, or the second
1087
+ render of an unchanged script loses every 2D selection. It follows the
1088
+ same rule a solid's runs do: across renders the old ID is unknown, so it
1089
+ goes to the reused node -- here the whole translate() statement."""
1090
+ def two_renders(src):
1091
+ cache = ManifoldCache()
1092
+ return _picked_text(tmp_path, src, cache), _picked_text(tmp_path, src, cache)
1093
+
1094
+ flat = two_renders("square(3);\ntranslate([10, 0]) circle(4);\n")
1095
+ solid = two_renders("cube(3);\ntranslate([10, 0, 0]) sphere(4);\n")
1096
+ assert all(t is not None for render in flat for body in render for t in body)
1097
+ assert flat[1] == [["square(3);"], ["translate([10, 0]) circle(4);"]]
1098
+ assert solid[1] == [["cube(3);"], ["translate([10, 0, 0]) sphere(4);"]]
1099
+
1100
+
1101
+ def test_a_viewer_can_ask_for_a_thin_2d_slab_and_export_keeps_1(tmp_path):
1102
+ """flat_preview_height thins the DISPLAYED slab only: a 2D-only script
1103
+ exports that slab, and it has to stay the reference's 1 unit."""
1104
+ import openscad_cpp_evaluator as E
1105
+ script = tmp_path / "flat.scad"
1106
+ script.write_text("square(10);\n")
1107
+
1108
+ def z_extent(bodies):
1109
+ verts = bodies[0].body.to_mesh().vert_properties
1110
+ return float(verts[:, 2].max() - verts[:, 2].min())
1111
+
1112
+ default = E.Evaluator()
1113
+ assert abs(z_extent(default.evaluate(str(script), {})[0]) - 1.0) < 1e-9
1114
+ thin = E.Evaluator(flat_preview_height=0.01)
1115
+ bodies, id_to_node = thin.evaluate(str(script), {})
1116
+ assert abs(z_extent(bodies) - 0.01) < 1e-9
1117
+ assert id_to_node, "still pickable"
1118
+ out = tmp_path / "flat.off" # ASCII: header, counts, vertices
1119
+ E.export_model(str(out), thin.geometry)
1120
+ lines = [l for l in out.read_text().splitlines() if l.strip() and not l.startswith("#")]
1121
+ nverts = int(lines[1].split()[0])
1122
+ zs = [float(l.split()[2]) for l in lines[2:2 + nverts]]
1123
+ assert abs(max(zs) - min(zs) - 1.0) < 1e-6, "export is the 1-unit slab"