openscad-cpp-evaluator 1.29.0__tar.gz → 1.31.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 (228) hide show
  1. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/CLAUDE.md +45 -0
  2. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/bindings/module.cpp +22 -3
  4. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/evaluator.hpp +30 -0
  5. openscad_cpp_evaluator-1.31.0/include/openscad_cpp_evaluator/segments.hpp +58 -0
  6. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/pyproject.toml +1 -1
  7. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/python/openscad_cpp_evaluator/__init__.py +9 -4
  8. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/extrude.cpp +190 -20
  9. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/function_builtins.cpp +1 -0
  10. openscad_cpp_evaluator-1.31.0/src/builtins/segments.cpp +256 -0
  11. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/text.cpp +4 -1
  12. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/bytecode_vm.cpp +1 -0
  13. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/csg_generate.cpp +1 -0
  14. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/debug_profile.cpp +1 -0
  15. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/user_calls.cpp +3 -0
  16. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/CMakeLists.txt +1 -0
  17. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_control_flow.cpp +99 -0
  18. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_csg_tree.cpp +4 -3
  19. openscad_cpp_evaluator-1.31.0/tests/test_discretizer.cpp +132 -0
  20. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_python_bindings.py +22 -0
  21. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/uv.lock +1 -1
  22. openscad_cpp_evaluator-1.29.0/include/openscad_cpp_evaluator/segments.hpp +0 -19
  23. openscad_cpp_evaluator-1.29.0/src/builtins/segments.cpp +0 -31
  24. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/.github/workflows/ci.yml +0 -0
  25. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/.github/workflows/wheels.yml +0 -0
  26. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/.gitignore +0 -0
  27. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/.gitmodules +0 -0
  28. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/LICENSE +0 -0
  30. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/README.md +0 -0
  31. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/bindings/CMakeLists.txt +0 -0
  32. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/bindings/ast_to_py.cpp +0 -0
  33. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/cmake/embed_font.cmake +0 -0
  34. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/examples/CMakeLists.txt +0 -0
  35. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/examples/eval_perf_benchmark.cpp +0 -0
  36. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/examples/manifold_cache_reuse.cpp +0 -0
  37. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/examples/minimal_debugger.cpp +0 -0
  38. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  39. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/.gitignore +0 -0
  40. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  41. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  42. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/README.md +0 -0
  43. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  44. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  45. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  46. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  47. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  48. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  49. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  50. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  51. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  52. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  53. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  54. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  55. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  56. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  57. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  58. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  59. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  60. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  61. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  62. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  63. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  64. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  65. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  66. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  67. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  68. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  69. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  70. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  71. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  72. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  73. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  74. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  75. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  76. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  77. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  78. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  79. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  80. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  81. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  82. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  83. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  84. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  85. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  86. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  87. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  88. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  89. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  90. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  91. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  92. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  93. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  94. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  95. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  96. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  97. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  98. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  99. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  100. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  101. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  102. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  103. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  104. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  105. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  106. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  107. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  108. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  109. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  110. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  111. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  112. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  113. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  114. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  115. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  116. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  117. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  118. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  119. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  120. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  121. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  122. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  123. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  124. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  125. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  126. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  127. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  128. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  129. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  130. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  131. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  132. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  133. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  134. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  135. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  136. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  137. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  138. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  139. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  140. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  141. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  142. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  143. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/resources/fonts/LICENSE +0 -0
  144. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  145. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  146. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  147. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  148. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/CMakeLists.txt +0 -0
  149. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/booleans.cpp +0 -0
  150. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/builtins.hpp +0 -0
  151. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/call_args.cpp +0 -0
  152. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/color.cpp +0 -0
  153. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/control.cpp +0 -0
  154. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/dxf_dim.cpp +0 -0
  155. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/import.cpp +0 -0
  156. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/modifiers.cpp +0 -0
  157. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/primitives_2d.cpp +0 -0
  158. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/primitives_3d.cpp +0 -0
  159. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/registry.cpp +0 -0
  160. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/roof.cpp +0 -0
  161. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/surface.cpp +0 -0
  162. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/topology.cpp +0 -0
  163. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/builtins/transforms.cpp +0 -0
  164. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/bytecode_compiler.cpp +0 -0
  165. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/colored_body.cpp +0 -0
  166. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/coverage.cpp +0 -0
  167. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/csg_resolve.cpp +0 -0
  168. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/css_colors.cpp +0 -0
  169. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/debug/debug_repl.cpp +0 -0
  170. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/eval_context.cpp +0 -0
  171. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/eval_error.cpp +0 -0
  172. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/eval_use.cpp +0 -0
  173. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/export.cpp +0 -0
  174. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/expr_eval.cpp +0 -0
  175. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/format_closure.cpp +0 -0
  176. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/import/dxf_import.cpp +0 -0
  177. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/import/mesh_import.cpp +0 -0
  178. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/import/surface_load.cpp +0 -0
  179. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/import/svg_import.cpp +0 -0
  180. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/manifold_cache.cpp +0 -0
  181. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/measure_geometry.cpp +0 -0
  182. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/mesh_check.cpp +0 -0
  183. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/stmt_eval.cpp +0 -0
  184. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/text/font_match.cpp +0 -0
  185. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/text/freetype_font_provider.cpp +0 -0
  186. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/text/text_metrics.cpp +0 -0
  187. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/utf8.cpp +0 -0
  188. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/value.cpp +0 -0
  189. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/xkcd_colors.cpp +0 -0
  190. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/xkcd_colors.hpp +0 -0
  191. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/src/zip_stored.cpp +0 -0
  192. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_booleans.cpp +0 -0
  193. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_bytecode_compiler.cpp +0 -0
  194. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_children_separate.cpp +0 -0
  195. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_cli.cpp +0 -0
  196. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_coverage.cpp +0 -0
  197. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_debug_hooks.cpp +0 -0
  198. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_dxf_svg_import.cpp +0 -0
  199. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_eval_context.cpp +0 -0
  200. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_export.cpp +0 -0
  201. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_expr_eval.cpp +0 -0
  202. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_extrude_roof.cpp +0 -0
  203. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_feature_detection.cpp +0 -0
  204. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_function_builtins.cpp +0 -0
  205. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_helpers.hpp +0 -0
  206. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_import_export.cpp +0 -0
  207. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_include_cache.cpp +0 -0
  208. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_manifold_cache.cpp +0 -0
  209. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_mesh_check.cpp +0 -0
  210. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_multi_color_merge.cpp +0 -0
  211. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_primitives.cpp +0 -0
  212. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_profiling.cpp +0 -0
  213. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_render_expr.cpp +0 -0
  214. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_scope_trail.cpp +0 -0
  215. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_scoping.cpp +0 -0
  216. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_surface.cpp +0 -0
  217. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_tail_calls.cpp +0 -0
  218. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_text.cpp +0 -0
  219. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_transforms.cpp +0 -0
  220. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_unexpected_args.cpp +0 -0
  221. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_use.cpp +0 -0
  222. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_value.cpp +0 -0
  223. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_viewport_params.cpp +0 -0
  224. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tests/test_zip_stored.cpp +0 -0
  225. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tools/cli/CMakeLists.txt +0 -0
  226. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tools/cli/cli_lib.cpp +0 -0
  227. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tools/cli/cli_lib.hpp +0 -0
  228. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.31.0}/tools/cli/main.cpp +0 -0
@@ -1569,6 +1569,51 @@ than the current pause point). Ported identically (same qualifier syntax, same e
1569
1569
  - `tools/cli/main.cpp` — a 3-line wrapper: builds `args` from `argv`, calls `runCli(args)` with the
1570
1570
  real `std::cin`/`std::cout`/`std::cerr`, returns its exit code.
1571
1571
 
1572
+ ## Curve discretization: `$fn`/`$fe`/`$fa`/`$fs` (`Discretizer`, segments.hpp)
1573
+
1574
+ A port of upstream's `CurveDiscretizer` (src/core/CurveDiscretizer.cc): circle/arc segment counts,
1575
+ `linear_extrude` slice counts (helix, conical helix, diagonal) and outline splitting. Every count was
1576
+ checked against the 2026.02.01 binary (`--enable=discretization-by-error`), including volumes, since
1577
+ the side-quad diagonal choice changes a twisted solid without changing its triangle count.
1578
+
1579
+ - **`$fe`** (max radial error): `n = pi/acos(1 - fe/r)`, 5 once `fe/r >= 0.1909830056`, capped at the
1580
+ `$fa`/`$fs`=0.01 count; replaces `$fa`/`$fs` entirely; arcs scale the UNceiled n. Upstream gates it
1581
+ behind `--enable=discretization-by-error`; here it is always on (`supported_feature
1582
+ ("discretization-by-error")`). Not predefined in the root ctx, as upstream: a bare read warns.
1583
+ - **Bugs this fixed**, all pre-existing and all silent:
1584
+ - `linear_extrude(twist=)` without `slices` was ONE slice: 180 degrees collapsed to a straight prism.
1585
+ - `slices=N` gave N+1 slices (Manifold's `nDivisions` is the copies between the ends).
1586
+ - Twisted and non-uniformly-scaled extrusions never split their outline edges.
1587
+ - Partial `rotate_extrude(angle=)` packed the full circle's count into the arc (Manifold's
1588
+ `Revolve` takes the count for the arc it is given).
1589
+ - Text curves used `$fn` at radius 0, halved, instead of `circular(size)/8 + 1`.
1590
+ - A fractional `$fn` truncated instead of rounding up.
1591
+ - **Twisted/non-uniform extrusions are built by hand** (`extrudeTwisted`, extrude.cpp), not by
1592
+ `Manifold::Extrude`: their side quads are not planar and upstream splits each along its SHORTER
1593
+ diagonal (`sgn_vdiff`, ties broken by twist direction and hole-ness). `Manifold::Extrude` always picks
1594
+ the same diagonal: same triangle count, different volume (241 vs 254 for a 180-degree twist).
1595
+ Planar cases (no twist, uniform scale) and a top scaled to zero stay on `Manifold::Extrude`.
1596
+ - **1-2 section arcs** (short `rotate_extrude` angles at coarse settings): `Revolve` substitutes its own
1597
+ default below 3, so these are an extrude warped round Z. The warp inverts the solid for a positive
1598
+ angle, hence the mirror-in-Y dance.
1599
+ - Kept departure: `$fa`/`$fs` <= 0 fall back to 12/2 rather than upstream's clamp-to-0.01 with a warning.
1600
+ - Not ported: `linear_extrude(v=)` (accepted, ignored); DXF/SVG import arcs don't read the discretizer.
1601
+
1602
+ ## Cancel and the module recursion limit (#554)
1603
+
1604
+ - **`Evaluator::setCancelFlag(shared_ptr<atomic<bool>>)`** -- bound as `CancelSignal` /
1605
+ `Evaluator(cancel_signal=...)`. Once the flag is set, from any thread, evaluate() throws
1606
+ `EvalError("Render cancelled")` at its next check: the top of `checkDebug` (every interpreted
1607
+ statement and loop pass already calls it, hooks or not), `pollCancel()` every 1,024 VM
1608
+ instructions in `driveVm`, and each top-level node in `generateTreeImpl`. A single Manifold op
1609
+ already under way still finishes. Sticky, unlike `setFastContinueInterruptFlag`'s test-and-clear.
1610
+ A host's echo callback was the only way in before, so a script that printed nothing could not
1611
+ be stopped at all.
1612
+ - **`kMaxModuleCallDepth = 16384`** nested user module calls, checked in `enterUserCall` against
1613
+ `moduleCallDepth_` -- "Recursion detected calling module 'm'", OpenSCAD's wording. Functions
1614
+ keep `kMaxVmCallStackDepth` (1e6). `module m() { m(); }` went from 4.2 s / 3.3 GB to stopping
1615
+ at once; OpenSCAD 2026.02.01 stops the same script near 14,000.
1616
+
1572
1617
  ## Coverage
1573
1618
 
1574
1619
  `Evaluator(coverage=true)` records which parts of a run's source actually executed and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.29.0
3
+ Version: 1.31.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -503,10 +503,22 @@ nb::object coverageResultToPy(const std::optional<oscadeval::CoverageResult>& cr
503
503
  return result;
504
504
  }
505
505
 
506
+ // A render's Cancel: .request() from any thread makes the evaluate() it was
507
+ // passed to throw EvalError("Render cancelled") at its next check -- see
508
+ // Evaluator::setCancelFlag. One per render; it is never cleared.
509
+ class CancelSignal {
510
+ public:
511
+ void request() { flag_->store(true, std::memory_order_relaxed); }
512
+ const std::shared_ptr<std::atomic<bool>>& flag() const { return flag_; }
513
+
514
+ private:
515
+ std::shared_ptr<std::atomic<bool>> flag_ = std::make_shared<std::atomic<bool>>(false);
516
+ };
517
+
506
518
  nb::object evaluate(const std::string& path, nb::dict viewportParams,
507
519
  std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, bool profile,
508
520
  bool generate, bool strictCommas, bool coverage, bool keepMinuendColor,
509
- double flatPreviewHeight) {
521
+ double flatPreviewHeight, CancelSignal* cancelSignal) {
510
522
  std::unordered_map<std::string, oscadeval::Value> vp = toViewportParams(viewportParams);
511
523
 
512
524
  std::vector<oscadeval::ColoredBody> bodies;
@@ -535,6 +547,7 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
535
547
  oscadeval::ResolvedUseScopes used = oscadeval::resolveUseScopes(program.nodes, path, logFn);
536
548
  oscadeval::Evaluator ev(logFn, nullptr, manifoldCache, oscadeval::DebugHooks{}, profile, coverage);
537
549
  ev.keepMinuendColor = keepMinuendColor;
550
+ if (cancelSignal) ev.setCancelFlag(cancelSignal->flag());
538
551
  ev.setUsedFileGlobals(used.usedFileGlobals);
539
552
  oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
540
553
  std::vector<oscadeval::ColoredBody> raw = ev.evaluate(used.processedNodes, ctx, vp, generate);
@@ -924,6 +937,10 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
924
937
 
925
938
  // See FastContinueSignal's own doc comment, above -- only a
926
939
  // constructor and request() are ever called from Python.
940
+ nb::class_<CancelSignal>(m, "CancelSignal")
941
+ .def(nb::init<>())
942
+ .def("request", &CancelSignal::request);
943
+
927
944
  nb::class_<FastContinueSignal>(m, "FastContinueSignal")
928
945
  .def(nb::init<>())
929
946
  .def("request", &FastContinueSignal::request);
@@ -993,7 +1010,7 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
993
1010
  m.def("evaluate", &evaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("manifold_cache") = nullptr,
994
1011
  nb::arg("profile") = false, nb::arg("generate") = true, nb::arg("strict_commas") = false,
995
1012
  nb::arg("coverage") = false, nb::arg("keep_minuend_color") = false,
996
- nb::arg("flat_preview_height") = oscadeval::kTopLevel2dHeight,
1013
+ nb::arg("flat_preview_height") = oscadeval::kTopLevel2dHeight, nb::arg("cancel_signal") = nullptr,
997
1014
  "Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit, "
998
1015
  "geometry, coverage_result).\n"
999
1016
  "coverage=True records which statements, branch arms and bodies ran: coverage_result is a dict "
@@ -1006,7 +1023,9 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
1006
1023
  "are populated.\n\n"
1007
1024
  "strict_commas=True makes a trailing comma in a call argument list or a let/for "
1008
1025
  "assignment list a syntax error, as OpenSCAD 2021.01 did. List literals and parameter "
1009
- "declarations keep theirs, which 2021.01 accepted too.");
1026
+ "declarations keep theirs, which 2021.01 accepted too.\n\n"
1027
+ "cancel_signal: a CancelSignal whose request(), from any thread, stops this call with "
1028
+ "EvalError('Render cancelled') -- even for a script that prints nothing.");
1010
1029
  m.def("parse_decls", &parseDecls, nb::arg("path"),
1011
1030
  "Parse a .scad file; return top-level declaration (namespace, name, start, end, line, column, origin) tuples.");
1012
1031
  m.def("strip_slivers", &stripSliversPy, nb::arg("verts"), nb::arg("tris"),
@@ -703,6 +703,23 @@ public:
703
703
  fastContinueInterrupt_ = std::move(flag);
704
704
  }
705
705
 
706
+ // A host's Cancel button. Set from any thread; the running evaluate()
707
+ // then throws EvalError("Render cancelled") at its next check: every
708
+ // interpreted statement and loop pass (checkDebug, which all of them
709
+ // already call), every 1024th VM instruction, and between top-level
710
+ // generate-pass nodes. A single Manifold operation already under way
711
+ // still runs to its end. Before this existed a host could only stop a
712
+ // script from its echo callback, so one that printed nothing ran to
713
+ // completion however long that took (#554). Sticky: never cleared here.
714
+ void setCancelFlag(std::shared_ptr<std::atomic<bool>> flag) { cancel_ = std::move(flag); }
715
+ void checkCancel() const {
716
+ if (cancel_ && cancel_->load(std::memory_order_relaxed)) throw EvalError("Render cancelled");
717
+ }
718
+ // The VM's per-instruction form: one relaxed load per 1024 instructions.
719
+ void pollCancel() {
720
+ if (cancel_ && (++cancelTicks_ & 0x3FF) == 0) checkCancel();
721
+ }
722
+
706
723
  // Checks whether a debug pause should happen at `node` (via the
707
724
  // injected DebugHooks::debugHook, if any -- a no-op otherwise),
708
725
  // applying any `mods` the hook returns to `ctx.let_` and throwing
@@ -1759,6 +1776,16 @@ public:
1759
1776
  // recalibrating for real.
1760
1777
  static constexpr size_t kMaxVmCallStackDepth = 1'000'000;
1761
1778
 
1779
+ // Nested user MODULE calls, of any modules. Functions keep
1780
+ // kMaxVmCallStackDepth: a deep function recursion builds nothing, but a
1781
+ // module frame holds its whole subtree, so `module m() { m(); }` used
1782
+ // to run to a million frames and 3.3 GB before stopping. OpenSCAD stops
1783
+ // the same script at ~14,000 ("Recursion detected calling module"),
1784
+ // 45 MB; this is that order, and far past any real library (BOSL2
1785
+ // nests tens deep). Checked in enterUserCall, which every module call
1786
+ // -- interpreted or compiled -- goes through (#554).
1787
+ static constexpr int kMaxModuleCallDepth = 16384;
1788
+
1762
1789
  // Counts native C++ nesting of driveVm itself -- NOT logical call
1763
1790
  // depth (that's vmCallStack_.size()/callStack_.size(), both of which
1764
1791
  // grow just as much for a pure VM-internal Op::CallModule hop, which
@@ -2074,6 +2101,9 @@ private:
2074
2101
  // default) means hook-skippable mode, if ever engaged, can't be
2075
2102
  // interrupted from outside a hook call.
2076
2103
  std::shared_ptr<std::atomic<bool>> fastContinueInterrupt_;
2104
+ // See setCancelFlag. nullptr (the default): nothing can cancel.
2105
+ std::shared_ptr<std::atomic<bool>> cancel_;
2106
+ unsigned cancelTicks_ = 0;
2077
2107
  // The root EvalContext resolveTree() was called with -- lets
2078
2108
  // checkDebug()'s locals snapshot fall back to top-level script
2079
2109
  // variables when paused inside a nested user call, the same way the
@@ -0,0 +1,58 @@
1
+ #pragma once
2
+
3
+ #include "openscad_cpp_evaluator/eval_context.hpp"
4
+ #include "openscad_cpp_evaluator/csg_node.hpp"
5
+
6
+ #include <manifold/common.h>
7
+
8
+ #include <optional>
9
+
10
+ namespace oscadeval {
11
+
12
+ // How curves become polygons: OpenSCAD's CurveDiscretizer (src/core/
13
+ // CurveDiscretizer.cc, upstream master cc19000e0), ported. Every count here
14
+ // matches the 2026.02.01 binary's.
15
+ //
16
+ // $fn > $fe > $fa/$fs. $fe is the most a curve's polygon may stray from the
17
+ // true circle, measured along a radius to an edge's midpoint; when set (>=
18
+ // 2^-20) it replaces $fa/$fs entirely. Upstream reads $fe only behind
19
+ // --enable=discretization-by-error; this evaluator always honours it, and
20
+ // advertises that as supported_feature("discretization-by-error").
21
+ //
22
+ // Departures from upstream, kept from before this port: $fa/$fs <= 0 fall
23
+ // back to their defaults (12/2) rather than clamping to 0.01 with a warning.
24
+ struct Discretizer {
25
+ double fn = 0.0;
26
+ double fe = 0.0;
27
+ double fa = 12.0;
28
+ double fs = 2.0;
29
+
30
+ static Discretizer fromCtx(const EvalContext& ctx);
31
+ // Stored in, and read back from, a node's params -- they are part of its
32
+ // cache key, and generate has only the params to go on.
33
+ static Discretizer fromParams(const CSGParams& params);
34
+ void store(CSGParams& params) const;
35
+
36
+ // Segments for `angle` degrees of arc at radius r. nullopt where
37
+ // upstream has none (r below 2^-20, a non-finite $fn/$fe/angle): every
38
+ // caller supplies its own fallback, as upstream's value_or() does.
39
+ std::optional<int> circular(double r, double angleDegrees = 360.0) const;
40
+
41
+ // linear_extrude slice counts: twist without scale, twist with uniform
42
+ // scale, and non-uniform scale (with or without twist).
43
+ std::optional<int> helixSlices(double rSqr, double height, double twistDegrees) const;
44
+ std::optional<int> conicalHelixSlices(double rSqr, double height, double twistDegrees, double scale) const;
45
+ std::optional<int> diagonalSlices(double deltaSqr, double height) const;
46
+
47
+ // Subdivide an outline's edges so a twisted or non-uniformly scaled
48
+ // extrusion keeps its shape between slices. `segments` is linear_extrude's
49
+ // own argument (0 = not given).
50
+ manifold::SimplePolygon splitOutline(const manifold::SimplePolygon& outline, double twistDegrees,
51
+ double scaleX, double scaleY, unsigned slices, unsigned segments) const;
52
+ };
53
+
54
+ // Full-circle segment count from ctx's $fn/$fe/$fa/$fs at radius r;
55
+ // upstream's primitives fall back to 3.
56
+ int fnSegmentsFromCtx(const EvalContext& ctx, double r = 0.0);
57
+
58
+ } // namespace oscadeval
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.29.0"
7
+ version = "1.31.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -13,12 +13,12 @@ from dataclasses import dataclass, field
13
13
  from typing import Optional
14
14
 
15
15
  from . import _openscad_cpp_evaluator as _ext
16
- from ._openscad_cpp_evaluator import FastContinueSignal, ManifoldCache
16
+ from ._openscad_cpp_evaluator import CancelSignal, FastContinueSignal, ManifoldCache
17
17
 
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", "format_source",
21
+ "CancelSignal", "FastContinueSignal", "parse_ast", "parse_ast_string", "format_source",
22
22
  "check_mesh", "strip_slivers",
23
23
  "export_model", "export_extensions", "list_fonts",
24
24
  ]
@@ -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, flat_preview_height=1.0):
568
+ keep_minuend_color=False, flat_preview_height=1.0, cancel_signal=None):
569
569
  self._echo_fn = echo_fn
570
570
  self._debug_hook = debug_hook
571
571
  self._error_break_fn = error_break_fn
@@ -574,6 +574,11 @@ class Evaluator:
574
574
  self._profile = profile
575
575
  self._fast_continue_signal = fast_continue_signal
576
576
  self._coverage = coverage
577
+ # A CancelSignal: request() it from any thread and a running
578
+ # evaluate() raises EvalError("Render cancelled") -- the only way to
579
+ # stop a script that prints nothing. Plain path only; the debugger
580
+ # has its own stop.
581
+ self._cancel_signal = cancel_signal
577
582
  # keep_minuend_color=True: difference() paints the faces a
578
583
  # subtrahend exposes with the MINUEND's colour rather than the
579
584
  # subtrahend's (or the cut green). A viewer option; see
@@ -640,7 +645,7 @@ class Evaluator:
640
645
  dyn_explicit, geometry, coverage_result) = _ext.evaluate(
641
646
  source_path, vp, self._manifold_cache, self._profile, generate,
642
647
  strict_commas, self._coverage, self._keep_minuend_color,
643
- self._flat_preview_height)
648
+ self._flat_preview_height, self._cancel_signal)
644
649
  # The evaluated bodies, still on the C++ side. Stashed like
645
650
  # csg_tree/profile_result rather than returned, so
646
651
  # evaluate()'s own 2-tuple result is unchanged -- callers
@@ -5,7 +5,9 @@
5
5
  #include "openscad_cpp_evaluator/segments.hpp"
6
6
 
7
7
  #include <algorithm>
8
+ #include <manifold/polygon.h>
8
9
  #include <cmath>
10
+ #include <numbers>
9
11
 
10
12
  namespace oscadeval {
11
13
 
@@ -26,13 +28,110 @@ namespace oscadeval {
26
28
  // still not covered by PushBuiltinWrap) is behaviorally unobservable --
27
29
  // see computeRoofParams's own doc comment (roof.cpp) for the one builtin
28
30
  // in this group where that ISN'T true and a different split was needed.
31
+ namespace {
32
+
33
+ // Upstream's sgn_vdiff: which of two diagonals is shorter, treating lengths
34
+ // within 1 part in 1e5 as a tie.
35
+ int sgnVdiff(const manifold::vec2& v1, const manifold::vec2& v2) {
36
+ const double l1 = manifold::la::length(v1), l2 = manifold::la::length(v2);
37
+ return 2 * std::fabs(l1 - l2) * 1e5 > l1 + l2 ? (l1 < l2 ? -1 : 1) : 0;
38
+ }
39
+
40
+ // A twisted or non-uniformly scaled extrusion, built the way upstream's
41
+ // linear_extrude.cc builds it. Its side quads are not planar, so which
42
+ // diagonal splits each one changes the solid, and Manifold's own Extrude
43
+ // always picks the same one: the result had OpenSCAD's triangle count and a
44
+ // different volume (241 vs 254 for a 180-degree twist of square(5)).
45
+ // Upstream splits along the SHORTER diagonal, breaking exact ties by twist
46
+ // direction and whether the outline is a hole.
47
+ manifold::Manifold extrudeTwisted(const manifold::Polygons& polys, double height, int slices, double twist,
48
+ double scaleX, double scaleY) {
49
+ size_t stride = 0;
50
+ for (const manifold::SimplePolygon& o : polys) stride += o.size();
51
+ const auto ringPoint = [&](const manifold::vec2& v, int j) {
52
+ const double t = static_cast<double>(j) / slices;
53
+ const double a = -twist * t * std::numbers::pi / 180.0;
54
+ return manifold::vec2((v.x * std::cos(a) - v.y * std::sin(a)) * (1 - (1 - scaleX) * t),
55
+ (v.x * std::sin(a) + v.y * std::cos(a)) * (1 - (1 - scaleY) * t));
56
+ };
57
+
58
+ manifold::MeshGL64 mesh;
59
+ mesh.numProp = 3;
60
+ for (int j = 0; j <= slices; ++j) {
61
+ for (const manifold::SimplePolygon& o : polys) {
62
+ for (const manifold::vec2& v : o) {
63
+ const manifold::vec2 p = ringPoint(v, j);
64
+ mesh.vertProperties.insert(mesh.vertProperties.end(),
65
+ {p.x, p.y, height * j / slices});
66
+ }
67
+ }
68
+ }
69
+ const auto tri = [&](size_t a, size_t b, size_t c) { mesh.triVerts.insert(mesh.triVerts.end(), {a, b, c}); };
70
+
71
+ const bool backTwist = twist <= 0; // rotation_slice_top <= rotation_slice_bottom, every slice
72
+ for (int j = 1; j <= slices; ++j) {
73
+ const size_t bot = (j - 1) * stride, top = j * stride;
74
+ size_t cur = 0;
75
+ for (const manifold::SimplePolygon& o : polys) {
76
+ const size_t n = o.size();
77
+ double area2 = 0;
78
+ for (size_t i = 0; i < n; ++i) area2 += o[i].x * o[(i + 1) % n].y - o[(i + 1) % n].x * o[i].y;
79
+ const bool flip = (area2 < 0) != backTwist; // !positive xor back_twist
80
+ manifold::vec2 prevBot = ringPoint(o[0], j - 1), prevTop = ringPoint(o[0], j);
81
+ for (size_t i = 1; i <= n; ++i) {
82
+ const manifold::vec2 vBot = ringPoint(o[i % n], j - 1), vTop = ringPoint(o[i % n], j);
83
+ const size_t idx = cur + i % n, prev = cur + i - 1;
84
+ const int diff = sgnVdiff(prevBot - vTop, vBot - prevTop);
85
+ if (diff == -1 || (diff == 0 && !flip)) {
86
+ tri(bot + idx, top + idx, bot + prev);
87
+ tri(top + prev, bot + prev, top + idx);
88
+ } else {
89
+ tri(bot + idx, top + prev, bot + prev);
90
+ tri(bot + idx, top + idx, top + prev);
91
+ }
92
+ prevBot = vBot;
93
+ prevTop = vTop;
94
+ }
95
+ cur += n;
96
+ }
97
+ }
98
+
99
+ // Caps: one triangulation of the outline, reused at both ends.
100
+ manifold::PolygonsIdx indexed;
101
+ size_t cur = 0;
102
+ for (const manifold::SimplePolygon& o : polys) {
103
+ manifold::SimplePolygonIdx ring;
104
+ for (size_t i = 0; i < o.size(); ++i) ring.push_back({o[i], static_cast<int>(cur + i)});
105
+ indexed.push_back(std::move(ring));
106
+ cur += o.size();
107
+ }
108
+ const size_t topBase = static_cast<size_t>(slices) * stride;
109
+ for (const manifold::ivec3& t : manifold::TriangulateIdx(indexed)) {
110
+ tri(t.x, t.z, t.y); // bottom faces down
111
+ tri(topBase + t.x, topBase + t.y, topBase + t.z); // top faces up
112
+ }
113
+ return manifold::Manifold(mesh);
114
+ }
115
+
116
+ } // namespace
117
+
29
118
  BuiltinWrapParams computeLinearExtrudeParams(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
30
119
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
31
120
 
32
121
  const double height = toDoubleLenient(getArg(args, 0, "height", Value{1.0}));
33
122
  const bool center = truthy(getArg(args, std::nullopt, "center", Value{false}));
34
123
  const double twist = toDoubleLenient(getArg(args, std::nullopt, "twist", Value{0.0}));
35
- const int slices = static_cast<int>(toDoubleLenient(getArg(args, std::nullopt, "slices", Value{0.0})));
124
+ // Upstream's validate_integral: any finite number counts as given, and is
125
+ // truncated and clamped (slices >= 1, segments >= 0). Not given, the
126
+ // discretizer decides.
127
+ const auto integral = [&](const char* name, double lo) -> std::optional<double> {
128
+ const Value v = getArg(args, std::nullopt, name, Value{});
129
+ const double* d = std::get_if<double>(&v);
130
+ if (!d || !std::isfinite(*d)) return std::nullopt;
131
+ return *d < lo ? lo : std::trunc(*d);
132
+ };
133
+ const std::optional<double> slices = integral("slices", 1.0);
134
+ const std::optional<double> segments = integral("segments", 0.0);
36
135
  const Value scaleArg = getArg(args, std::nullopt, "scale", Value{});
37
136
 
38
137
  double scaleX = 1.0, scaleY = 1.0;
@@ -47,7 +146,9 @@ BuiltinWrapParams computeLinearExtrudeParams(Evaluator& ev, const oscad::Modular
47
146
  params["height"] = Value{height};
48
147
  params["center"] = Value{center};
49
148
  params["twist"] = Value{twist};
50
- params["slices"] = Value{static_cast<double>(slices)};
149
+ params["slices"] = slices ? Value{*slices} : Value{};
150
+ params["segments"] = segments ? Value{*segments} : Value{};
151
+ Discretizer::fromCtx(effCtx).store(params);
51
152
  params["scale_x"] = Value{scaleX};
52
153
  params["scale_y"] = Value{scaleY};
53
154
  params["color"] = colorToValue(effCtx.color);
@@ -69,11 +170,60 @@ std::vector<ColoredBody> generateLinearExtrude(Evaluator& ev, const CSGParams& p
69
170
  if (!cs || cs->IsEmpty()) return {};
70
171
 
71
172
  const double height = std::get<double>(params.at("height"));
72
- const int slices = static_cast<int>(std::get<double>(params.at("slices")));
73
173
  const double twist = std::get<double>(params.at("twist"));
74
- const manifold::vec2 scaleTop(std::get<double>(params.at("scale_x")), std::get<double>(params.at("scale_y")));
174
+ const double scaleX = std::get<double>(params.at("scale_x"));
175
+ const double scaleY = std::get<double>(params.at("scale_y"));
176
+ const double* givenSlices = std::get_if<double>(&params.at("slices"));
177
+ const double* givenSegments = std::get_if<double>(&params.at("segments"));
178
+ const Discretizer disc = Discretizer::fromParams(params);
179
+ manifold::Polygons polys = cs->ToPolygons();
180
+
181
+ // Upstream's calc_num_slices (linear_extrude.cc).
182
+ const auto maxDeltaSqr = [&] {
183
+ double m = 0;
184
+ for (const manifold::SimplePolygon& o : polys)
185
+ for (const manifold::vec2& v : o) m = std::max(m, manifold::la::length2(v - manifold::vec2(v.x * scaleX, v.y * scaleY)));
186
+ return m;
187
+ };
188
+ const int twistFallback = std::max(static_cast<int>(std::ceil(twist / 120.0)), 1);
189
+ int slices = 1;
190
+ if (givenSlices) {
191
+ slices = static_cast<int>(*givenSlices);
192
+ } else if (twist != 0.0) {
193
+ double maxR1Sqr = 0;
194
+ for (const manifold::SimplePolygon& o : polys)
195
+ for (const manifold::vec2& v : o) maxR1Sqr = std::max(maxR1Sqr, manifold::la::length2(v));
196
+ if (scaleX == 1.0 && scaleY == 1.0) {
197
+ slices = disc.helixSlices(maxR1Sqr, height, twist).value_or(twistFallback);
198
+ } else if (scaleX != scaleY) {
199
+ slices = std::max(disc.diagonalSlices(maxDeltaSqr(), height).value_or(1),
200
+ disc.helixSlices(maxR1Sqr, height, twist).value_or(twistFallback));
201
+ } else {
202
+ slices = disc.conicalHelixSlices(maxR1Sqr, height, twist, scaleX).value_or(twistFallback);
203
+ }
204
+ } else if (scaleX != scaleY) {
205
+ slices = disc.diagonalSlices(maxDeltaSqr(), height).value_or(1);
206
+ }
207
+
208
+ // Split outline edges where a straight one would lose the shape between
209
+ // slices: twist or non-uniform scale, or `segments` asked for. segments=0
210
+ // turns it off.
211
+ const unsigned segments = givenSegments ? static_cast<unsigned>(*givenSegments) : 0;
212
+ const bool nonLinear = twist != 0.0 || scaleX != scaleY;
213
+ if (!(givenSegments && segments == 0) && (segments > 0 || nonLinear)) {
214
+ for (manifold::SimplePolygon& o : polys)
215
+ o = disc.splitOutline(o, twist, scaleX, scaleY, static_cast<unsigned>(slices), segments);
216
+ }
75
217
 
76
- manifold::Manifold body = manifold::Manifold::Extrude(cs->ToPolygons(), height, slices, -twist, scaleTop);
218
+ // Planar side quads (no twist, uniform scale) come out the same however
219
+ // they are split, so Manifold's own Extrude does. So does a top scaled to
220
+ // zero in either axis, where upstream's shorter-diagonal rule is
221
+ // reversed to avoid zero-thickness ears; a cone tip is Extrude's to make.
222
+ // Manifold's nDivisions is the copies BETWEEN the ends: slices - 1.
223
+ manifold::Manifold body =
224
+ nonLinear && scaleX > 0 && scaleY > 0 && height > 0
225
+ ? extrudeTwisted(polys, height, slices, twist, scaleX, scaleY)
226
+ : manifold::Manifold::Extrude(polys, height, slices - 1, -twist, manifold::vec2(scaleX, scaleY));
77
227
  if (std::get<bool>(params.at("center"))) body = body.Translate(manifold::vec3(0, 0, -height / 2));
78
228
  return {ev.tagGenerated(std::move(body), node, params.at("color"))};
79
229
  }
@@ -91,18 +241,9 @@ BuiltinWrapParams computeRotateExtrudeParams(Evaluator& ev, const oscad::Modular
91
241
 
92
242
  const double angle = toDoubleLenient(getArg(args, 0, "angle", Value{360.0}));
93
243
 
94
- const auto dynOr = [&](const char* name, double fallback) {
95
- const Value* v = effCtx.dyn->find(name);
96
- if (!v) return fallback;
97
- const double* d = std::get_if<double>(v);
98
- return d ? *d : fallback;
99
- };
100
-
101
244
  CSGParams params;
102
245
  params["angle"] = Value{angle};
103
- params["fn"] = Value{dynOr("$fn", 0.0)};
104
- params["fa"] = Value{dynOr("$fa", 12.0)};
105
- params["fs"] = Value{dynOr("$fs", 2.0)};
246
+ Discretizer::fromCtx(effCtx).store(params);
106
247
  params["color"] = colorToValue(effCtx.color);
107
248
  return BuiltinWrapParams{std::move(params), std::move(effCtx)};
108
249
  }
@@ -121,12 +262,41 @@ std::vector<ColoredBody> generateRotateExtrude(Evaluator& ev, const CSGParams& p
121
262
  const std::optional<manifold::CrossSection> cs = toCrossSection(flattenCsgTree(children));
122
263
  if (!cs || cs->IsEmpty()) return {};
123
264
 
265
+ // Upstream (rotate_extrude.cc) sizes the arc by the profile's extent in X,
266
+ // measured from the axis: both ends start at 0.
124
267
  const manifold::Rect bounds = cs->Bounds();
125
- const double maxX = std::max(std::fabs(bounds.min.x), std::fabs(bounds.max.x));
126
- const int segs = fnSegments(std::get<double>(params.at("fn")), std::get<double>(params.at("fa")),
127
- std::get<double>(params.at("fs")), maxX);
128
-
129
- manifold::Manifold body = manifold::Manifold::Revolve(cs->ToPolygons(), segs, std::get<double>(params.at("angle")));
268
+ const double width = std::max(bounds.max.x, 0.0) - std::min(bounds.min.x, 0.0);
269
+ const double angle = std::get<double>(params.at("angle"));
270
+ const int sections = Discretizer::fromParams(params).circular(width, angle).value_or(
271
+ std::max(1, static_cast<int>(std::fabs(angle) / 360 * 3)));
272
+ // Revolve takes this as the section count for the arc it is given -- not
273
+ // per full circle, which is what it used to be handed, packing a whole
274
+ // circle's worth into a partial one.
275
+ manifold::Manifold body;
276
+ if (sections >= 3 || bounds.min.x < 0) {
277
+ // (A profile left of the axis keeps Revolve's handling; a 1-2 section
278
+ // arc of one is the only case still drawn with 3.)
279
+ body = manifold::Manifold::Revolve(cs->ToPolygons(), std::max(sections, 3), angle);
280
+ } else {
281
+ // Revolve substitutes its own default below 3 sections, but a short
282
+ // arc at coarse settings is 1 or 2 (upstream draws exactly that). So
283
+ // extrude the profile into that many sections and wrap them round the
284
+ // Z axis. The wrap turns the solid inside out for a positive angle, so
285
+ // then the profile goes in mirrored in Y and comes back out unmirrored.
286
+ const double sign = angle > 0 ? -1.0 : 1.0;
287
+ manifold::Polygons profile = cs->ToPolygons();
288
+ if (sign < 0) {
289
+ for (manifold::SimplePolygon& o : profile) {
290
+ for (manifold::vec2& v : o) v.y = -v.y;
291
+ std::reverse(o.begin(), o.end());
292
+ }
293
+ }
294
+ const double rad = angle * std::numbers::pi / 180.0;
295
+ body = manifold::Manifold::Extrude(profile, 1.0, sections - 1).Warp([rad, sign](manifold::vec3& v) {
296
+ const double a = v.z * rad;
297
+ v = manifold::vec3(v.x * std::cos(a), v.x * std::sin(a), sign * v.y);
298
+ });
299
+ }
130
300
  return {ev.tagGenerated(std::move(body), node, params.at("color"))};
131
301
  }
132
302
 
@@ -1017,6 +1017,7 @@ const std::unordered_map<std::string, double>& featureLevels() {
1017
1017
  {"object-function", 1.0}, // object(), unconditional here
1018
1018
  {"roof-op", 1.0}, // roof(), method="voronoi" only
1019
1019
  {"svg-class", 1.0}, // import(svg, class=); id= is upstream's
1020
+ {"discretization-by-error", 1.0}, // $fe, always on (upstream: behind --enable)
1020
1021
  };
1021
1022
  return levels;
1022
1023
  }