openscad-cpp-evaluator 1.28.6__tar.gz → 1.30.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 (226) hide show
  1. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/CLAUDE.md +24 -3
  2. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/bindings/module.cpp +22 -3
  4. openscad_cpp_evaluator-1.30.0/include/openscad_cpp_evaluator/css_colors.hpp +20 -0
  5. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/evaluator.hpp +30 -0
  6. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/pyproject.toml +1 -1
  7. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/python/openscad_cpp_evaluator/__init__.py +9 -4
  8. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/CMakeLists.txt +1 -0
  9. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/color.cpp +44 -17
  10. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/bytecode_vm.cpp +1 -0
  11. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/csg_generate.cpp +1 -0
  12. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/css_colors.cpp +39 -31
  13. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/debug_profile.cpp +1 -0
  14. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/user_calls.cpp +3 -0
  15. openscad_cpp_evaluator-1.30.0/src/xkcd_colors.cpp +967 -0
  16. openscad_cpp_evaluator-1.30.0/src/xkcd_colors.hpp +13 -0
  17. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_control_flow.cpp +99 -0
  18. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_csg_tree.cpp +4 -3
  19. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_python_bindings.py +22 -0
  20. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_transforms.cpp +103 -30
  21. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/uv.lock +1 -1
  22. openscad_cpp_evaluator-1.28.6/include/openscad_cpp_evaluator/css_colors.hpp +0 -19
  23. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/.github/workflows/ci.yml +0 -0
  24. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/.github/workflows/wheels.yml +0 -0
  25. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/.gitignore +0 -0
  26. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/.gitmodules +0 -0
  27. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/LICENSE +0 -0
  29. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/README.md +0 -0
  30. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/bindings/CMakeLists.txt +0 -0
  31. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/bindings/ast_to_py.cpp +0 -0
  32. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/cmake/embed_font.cmake +0 -0
  33. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/examples/CMakeLists.txt +0 -0
  34. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/examples/eval_perf_benchmark.cpp +0 -0
  35. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/examples/manifold_cache_reuse.cpp +0 -0
  36. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/examples/minimal_debugger.cpp +0 -0
  37. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  38. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/.gitignore +0 -0
  39. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  40. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  41. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/README.md +0 -0
  42. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  43. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  44. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  45. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  46. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  47. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  48. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  49. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  50. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  51. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  52. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  53. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  54. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  55. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  56. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  57. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  58. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  59. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  60. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  61. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  62. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  63. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  64. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  65. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  66. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  67. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  68. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  69. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  70. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  71. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  72. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  73. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  74. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  75. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  76. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  77. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  78. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  79. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  80. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  81. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  82. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  83. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  84. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  85. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  86. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  87. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  88. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  89. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  90. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  91. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  92. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  93. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  94. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  95. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  96. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  97. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  98. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  99. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  100. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  101. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  102. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  103. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  104. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  105. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  106. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  107. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  108. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  109. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  110. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  111. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  112. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  113. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  114. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  115. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  116. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  117. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  118. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  119. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  120. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  121. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  122. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  123. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  124. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  125. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  126. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  127. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  128. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  129. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  130. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  131. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  132. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  133. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  134. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  135. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  136. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  137. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  138. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  139. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  140. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  141. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  142. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/resources/fonts/LICENSE +0 -0
  143. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  144. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  145. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  146. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  147. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/booleans.cpp +0 -0
  148. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/builtins.hpp +0 -0
  149. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/call_args.cpp +0 -0
  150. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/control.cpp +0 -0
  151. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/dxf_dim.cpp +0 -0
  152. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/extrude.cpp +0 -0
  153. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/function_builtins.cpp +0 -0
  154. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/import.cpp +0 -0
  155. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/modifiers.cpp +0 -0
  156. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/primitives_2d.cpp +0 -0
  157. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/primitives_3d.cpp +0 -0
  158. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/registry.cpp +0 -0
  159. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/roof.cpp +0 -0
  160. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/segments.cpp +0 -0
  161. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/surface.cpp +0 -0
  162. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/text.cpp +0 -0
  163. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/topology.cpp +0 -0
  164. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/builtins/transforms.cpp +0 -0
  165. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/bytecode_compiler.cpp +0 -0
  166. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/colored_body.cpp +0 -0
  167. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/coverage.cpp +0 -0
  168. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/csg_resolve.cpp +0 -0
  169. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/debug/debug_repl.cpp +0 -0
  170. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/eval_context.cpp +0 -0
  171. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/eval_error.cpp +0 -0
  172. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/eval_use.cpp +0 -0
  173. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/export.cpp +0 -0
  174. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/expr_eval.cpp +0 -0
  175. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/format_closure.cpp +0 -0
  176. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/import/dxf_import.cpp +0 -0
  177. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/import/mesh_import.cpp +0 -0
  178. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/import/surface_load.cpp +0 -0
  179. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/import/svg_import.cpp +0 -0
  180. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/manifold_cache.cpp +0 -0
  181. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/measure_geometry.cpp +0 -0
  182. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/mesh_check.cpp +0 -0
  183. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/stmt_eval.cpp +0 -0
  184. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/text/font_match.cpp +0 -0
  185. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/text/freetype_font_provider.cpp +0 -0
  186. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/text/text_metrics.cpp +0 -0
  187. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/utf8.cpp +0 -0
  188. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/value.cpp +0 -0
  189. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/src/zip_stored.cpp +0 -0
  190. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/CMakeLists.txt +0 -0
  191. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_booleans.cpp +0 -0
  192. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_bytecode_compiler.cpp +0 -0
  193. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_children_separate.cpp +0 -0
  194. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_cli.cpp +0 -0
  195. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_coverage.cpp +0 -0
  196. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_debug_hooks.cpp +0 -0
  197. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_dxf_svg_import.cpp +0 -0
  198. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_eval_context.cpp +0 -0
  199. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_export.cpp +0 -0
  200. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_expr_eval.cpp +0 -0
  201. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_extrude_roof.cpp +0 -0
  202. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_feature_detection.cpp +0 -0
  203. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_function_builtins.cpp +0 -0
  204. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_helpers.hpp +0 -0
  205. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_import_export.cpp +0 -0
  206. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_include_cache.cpp +0 -0
  207. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_manifold_cache.cpp +0 -0
  208. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_mesh_check.cpp +0 -0
  209. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_multi_color_merge.cpp +0 -0
  210. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_primitives.cpp +0 -0
  211. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_profiling.cpp +0 -0
  212. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_render_expr.cpp +0 -0
  213. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_scope_trail.cpp +0 -0
  214. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_scoping.cpp +0 -0
  215. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_surface.cpp +0 -0
  216. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_tail_calls.cpp +0 -0
  217. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_text.cpp +0 -0
  218. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_unexpected_args.cpp +0 -0
  219. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_use.cpp +0 -0
  220. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_value.cpp +0 -0
  221. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_viewport_params.cpp +0 -0
  222. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tests/test_zip_stored.cpp +0 -0
  223. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tools/cli/CMakeLists.txt +0 -0
  224. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tools/cli/cli_lib.cpp +0 -0
  225. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tools/cli/cli_lib.hpp +0 -0
  226. {openscad_cpp_evaluator-1.28.6 → openscad_cpp_evaluator-1.30.0}/tools/cli/main.cpp +0 -0
@@ -819,9 +819,15 @@ grep for `ponytail:`.
819
819
  geometric-correctness test in Phase 3, not a hypothetical.
820
820
  - `include/openscad_cpp_evaluator/segments.hpp`, `src/builtins/segments.cpp` — `$fn`/`$fa`/`$fs` ->
821
821
  circular-shape segment count (`fnSegments`/`fnSegmentsFromCtx`), used by sphere/cylinder/circle.
822
- - `include/openscad_cpp_evaluator/css_colors.hpp`, `src/css_colors.cpp` — the 148-entry CSS/SVG
823
- color-name table (mechanically ported from the Python reference's own generated table) plus hex
824
- parsing, backing `color()`'s string-name argument.
822
+ - `include/openscad_cpp_evaluator/css_colors.hpp`, `src/css_colors.cpp`, `src/xkcd_colors.{hpp,cpp}` —
823
+ `parseColor`, OpenSCAD's `parse_color` (ColorUtil.cc): CSS Color Level 4 names (148, with
824
+ `rebeccapurple`) plus `transparent` (alpha 0), `"xkcd:<name>"` over the 949 xkcd survey names
825
+ (generated from OpenSCAD's `XkcdColors.h`, CC0), all case-insensitive, then strict `#rgb` /
826
+ `#rgba` / `#rrggbb` / `#rrggbbaa`. `color()` (`src/builtins/color.cpp`) follows `builtin_color`:
827
+ an unreadable string warns `Unable to parse color "…"` and sets NO colour (children keep their
828
+ own, or the outer one) -- it used to draw white; a list fills missing channels with 1 and warns
829
+ outside 0..1; a numeric `alpha` replaces any alpha, a 4-list's included (checked on the 2026.02.01
830
+ binary's 3MF export). `cssColor` (white fallback) is only for SVG/PDF export options.
825
831
  - `src/builtins/{primitives_2d,primitives_3d,transforms,color,booleans,modifiers,control}.cpp` —
826
832
  one file per builtin group, each a `ResolveFn`/`GenerateFn` pair (or a few, where several builtin
827
833
  names share one pair — circle/square/polygon, translate/rotate/scale/mirror/multmatrix/resize,
@@ -1563,6 +1569,21 @@ than the current pause point). Ported identically (same qualifier syntax, same e
1563
1569
  - `tools/cli/main.cpp` — a 3-line wrapper: builds `args` from `argv`, calls `runCli(args)` with the
1564
1570
  real `std::cin`/`std::cout`/`std::cerr`, returns its exit code.
1565
1571
 
1572
+ ## Cancel and the module recursion limit (#554)
1573
+
1574
+ - **`Evaluator::setCancelFlag(shared_ptr<atomic<bool>>)`** -- bound as `CancelSignal` /
1575
+ `Evaluator(cancel_signal=...)`. Once the flag is set, from any thread, evaluate() throws
1576
+ `EvalError("Render cancelled")` at its next check: the top of `checkDebug` (every interpreted
1577
+ statement and loop pass already calls it, hooks or not), `pollCancel()` every 1,024 VM
1578
+ instructions in `driveVm`, and each top-level node in `generateTreeImpl`. A single Manifold op
1579
+ already under way still finishes. Sticky, unlike `setFastContinueInterruptFlag`'s test-and-clear.
1580
+ A host's echo callback was the only way in before, so a script that printed nothing could not
1581
+ be stopped at all.
1582
+ - **`kMaxModuleCallDepth = 16384`** nested user module calls, checked in `enterUserCall` against
1583
+ `moduleCallDepth_` -- "Recursion detected calling module 'm'", OpenSCAD's wording. Functions
1584
+ keep `kMaxVmCallStackDepth` (1e6). `module m() { m(); }` went from 4.2 s / 3.3 GB to stopping
1585
+ at once; OpenSCAD 2026.02.01 stops the same script near 14,000.
1586
+
1566
1587
  ## Coverage
1567
1588
 
1568
1589
  `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.28.6
3
+ Version: 1.30.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"),
@@ -0,0 +1,20 @@
1
+ #pragma once
2
+
3
+ #include <array>
4
+ #include <optional>
5
+ #include <string>
6
+
7
+ namespace oscadeval {
8
+
9
+ // A colour string as OpenSCAD's parse_color reads it (ColorUtil.cc):
10
+ // a CSS Color Level 4 name or OpenSCAD's "transparent" (alpha 0), an
11
+ // "xkcd:<name>" from the xkcd colour survey, all case-insensitive; else
12
+ // "#rgb", "#rgba", "#rrggbb" or "#rrggbbaa". Nothing if it is none of
13
+ // those -- color() then warns and leaves its children's colour alone.
14
+ std::optional<std::array<double, 4>> parseColor(const std::string& text);
15
+
16
+ // parseColor with the alpha replaced, and white for anything unparseable.
17
+ // For export options (SVG/PDF fill and stroke), which have no warning path.
18
+ std::array<double, 4> cssColor(const std::string& name, double alpha = 1.0);
19
+
20
+ } // namespace oscadeval
@@ -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
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.28.6"
7
+ version = "1.30.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
@@ -11,6 +11,7 @@ add_library(openscad_cpp_evaluator STATIC
11
11
  bytecode_vm.cpp
12
12
  colored_body.cpp
13
13
  css_colors.cpp
14
+ xkcd_colors.cpp
14
15
  csg_resolve.cpp
15
16
  coverage.cpp
16
17
  csg_generate.cpp
@@ -1,38 +1,63 @@
1
1
  #include "builtins.hpp"
2
2
 
3
+ #include <cstdio>
4
+
3
5
  #include "openscad_cpp_evaluator/call_args.hpp"
4
6
  #include "openscad_cpp_evaluator/css_colors.hpp"
5
7
  #include "openscad_cpp_evaluator/evaluator.hpp"
6
8
 
7
9
  namespace oscadeval {
8
10
 
9
- // color(c = [1,1,1,1], alpha = 1) -- c is a CSS/hex color name, an [r,g,b]
10
- // (alpha from the separate `alpha` argument), or an [r,g,b,a] (alpha taken
11
- // from the list itself, the separate `alpha` argument is ignored in that
12
- // case). Sets the color on ctx for every descendant statement. Mirrors
13
- // _resolve_color/_generate_color.
11
+ // color(c, alpha), as OpenSCAD's builtin_color (ColorNode.cc):
12
+ // - a string goes through parseColor (CSS / "xkcd:" names, hex with or
13
+ // without alpha); one it cannot read warns "Unable to parse color" and
14
+ // leaves the children's colour alone -- inherited from outside, not reset;
15
+ // - a list fills [r, g, b, a] in order, 1.0 for any it lacks, warning on a
16
+ // component outside 0..1;
17
+ // - a numeric `alpha` then replaces the alpha of whatever `c` gave -- even a
18
+ // 4-element list's, and even "#rrggbbaa"'s -- warning when outside 0..1;
19
+ // - anything else for `c`, or no `c` at all, sets no colour (OpenSCAD's
20
+ // unset Color4f is (-1,-1,-1,-1), which is not a colour).
21
+
22
+ namespace {
23
+ std::string oneDecimal(double v) {
24
+ char buf[64];
25
+ std::snprintf(buf, sizeof buf, "%.1f", v);
26
+ return buf;
27
+ }
28
+ } // namespace
14
29
 
15
30
  BuiltinWrapParams computeColorParams(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
16
31
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
17
- const Value defaultC =
18
- Value{std::make_shared<const ValueList>(ValueList{{Value{1.0}, Value{1.0}, Value{1.0}, Value{1.0}}})};
19
- Value cArg = getArg(args, 0, "c", defaultC);
20
- const double alpha = toDoubleLenient(getArg(args, 1, "alpha", Value{1.0}));
32
+ const Value cArg = getArg(args, 0, "c");
33
+ const Value alphaArg = getArg(args, 1, "alpha");
21
34
 
22
- std::array<double, 4> rgba{1.0, 1.0, 1.0, 1.0};
35
+ std::optional<std::array<double, 4>> rgba;
23
36
  if (const std::string* s = std::get_if<std::string>(&cArg)) {
24
- rgba = cssColor(*s, alpha);
37
+ rgba = parseColor(*s);
38
+ if (!rgba) ev.warn("Unable to parse color \"" + *s + "\"", &node.position());
25
39
  } else if (const ListPtr* l = std::get_if<ListPtr>(&cArg); l && *l) {
26
40
  const auto& items = (*l)->items;
27
- if (items.size() == 3) {
28
- rgba = {toDoubleLenient(items[0]), toDoubleLenient(items[1]), toDoubleLenient(items[2]), alpha};
29
- } else if (items.size() >= 4) {
30
- rgba = {toDoubleLenient(items[0]), toDoubleLenient(items[1]), toDoubleLenient(items[2]),
31
- toDoubleLenient(items[3])};
41
+ std::array<double, 4> c{1.0, 1.0, 1.0, 1.0};
42
+ for (size_t i = 0; i < 4; ++i) {
43
+ if (i < items.size()) c[i] = toDoubleLenient(items[i]);
44
+ if (c[i] > 1 || c[i] < 0)
45
+ ev.warn("color() expects numbers between 0.0 and 1.0. Value of " + oneDecimal(c[i]) +
46
+ " is out of range",
47
+ &node.position());
32
48
  }
49
+ rgba = c;
50
+ }
51
+ if (const double* a = std::get_if<double>(&alphaArg)) {
52
+ if (rgba) (*rgba)[3] = *a;
53
+ if (*a < 0.0 || *a > 1.0)
54
+ ev.warn("color() expects alpha between 0.0 and 1.0. Value of " + oneDecimal(*a) + " is out of range",
55
+ &node.position());
33
56
  }
34
57
 
35
- EvalContext childCtx = effCtx.childCtx(nullptr, std::optional<std::array<double, 4>>(rgba));
58
+ // No colour: the child context inherits the outer one (childCtx's
59
+ // nullopt), and generateColor passes the children through untouched.
60
+ EvalContext childCtx = effCtx.childCtx(nullptr, rgba);
36
61
  CSGParams params;
37
62
  params["rgba"] = colorToValue(rgba);
38
63
  return BuiltinWrapParams{std::move(params), std::move(childCtx)};
@@ -70,6 +95,8 @@ std::shared_ptr<const std::vector<ColoredBody>> recolorParts(const std::vector<C
70
95
  std::vector<ColoredBody> generateColor(Evaluator& ev, const CSGParams& params,
71
96
  const std::vector<std::unique_ptr<CSGNode>>& children, const oscad::ASTNode&) {
72
97
  const auto rgba = valueToColor(params.at("rgba"));
98
+ // An unreadable colour sets none: the children keep their own.
99
+ if (!rgba) return flattenCsgTree(children);
73
100
  std::vector<ColoredBody> result;
74
101
  for (ColoredBody b : flattenCsgTree(children)) {
75
102
  b.color = rgba;
@@ -609,6 +609,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
609
609
  continue;
610
610
  }
611
611
 
612
+ ev.pollCancel();
612
613
  const Instruction& ins = f.code[f.pc];
613
614
  EvalContext& ctx = f.ctxChain.back();
614
615
  switch (ins.op) {
@@ -35,6 +35,7 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
35
35
  const auto& dispatch = generateDispatch();
36
36
  for (CSGNode* nodePtr : tree) {
37
37
  CSGNode& node = *nodePtr;
38
+ checkCancel();
38
39
 
39
40
  // ManifoldCache lookup: node.uncacheable (rands() taint, set
40
41
  // during resolve -- see csg_resolve.cpp) always forces a miss,
@@ -1,16 +1,18 @@
1
1
  #include "openscad_cpp_evaluator/css_colors.hpp"
2
+ #include "xkcd_colors.hpp"
2
3
 
3
4
  #include <cctype>
5
+ #include <optional>
4
6
  #include <unordered_map>
5
7
 
6
8
  namespace oscadeval {
7
9
 
8
10
  namespace {
9
11
 
10
- // Generated (not hand-transcribed) from the Python reference's own
11
- // _css_colors.py, itself generated from a live PySide6 install -- see that
12
- // file's docstring for the regeneration recipe and the Qt-vs-CSS3 list
13
- // discrepancy (British-spelling aliases included, `rebeccapurple` excluded).
12
+ // Originally generated from the Python reference's _css_colors.py (itself
13
+ // from a live PySide6 install). Now OpenSCAD's own WebColors.h list: every
14
+ // RGB here matches it, `rebeccapurple` (CSS Color Level 4) is added, and
15
+ // `transparent`, which needs an alpha of 0, is handled in parseColor.
14
16
  const std::unordered_map<std::string, std::array<double, 3>>& colorTable() {
15
17
  static const std::unordered_map<std::string, std::array<double, 3>> table = {
16
18
  {"aliceblue", {0.9411764740943909, 0.9725490212440491, 1.0}},
@@ -132,6 +134,7 @@ const std::unordered_map<std::string, std::array<double, 3>>& colorTable() {
132
134
  {"plum", {0.8666666746139526, 0.6274510025978088, 0.8666666746139526}},
133
135
  {"powderblue", {0.6901960968971252, 0.8784313797950745, 0.9019607901573181}},
134
136
  {"purple", {0.501960813999176, 0.0, 0.501960813999176}},
137
+ {"rebeccapurple", {0.4, 0.2, 0.6}},
135
138
  {"red", {1.0, 0.0, 0.0}},
136
139
  {"rosybrown", {0.7372549176216125, 0.5607843399047852, 0.5607843399047852}},
137
140
  {"royalblue", {0.2549019753932953, 0.4117647111415863, 0.8823529481887817}},
@@ -153,7 +156,6 @@ const std::unordered_map<std::string, std::array<double, 3>>& colorTable() {
153
156
  {"teal", {0.0, 0.501960813999176, 0.501960813999176}},
154
157
  {"thistle", {0.8470588326454163, 0.7490196228027344, 0.8470588326454163}},
155
158
  {"tomato", {1.0, 0.38823530077934265, 0.27843138575553894}},
156
- {"transparent", {0.0, 0.0, 0.0}},
157
159
  {"turquoise", {0.250980406999588, 0.8784313797950745, 0.8156862854957581}},
158
160
  {"violet", {0.9333333373069763, 0.5098039507865906, 0.9333333373069763}},
159
161
  {"wheat", {0.9607843160629272, 0.8705882430076599, 0.7019608020782471}},
@@ -171,38 +173,44 @@ std::string toLower(const std::string& s) {
171
173
  return r;
172
174
  }
173
175
 
174
- int hexDigit(char c) {
175
- if (c >= '0' && c <= '9') return c - '0';
176
- if (c >= 'a' && c <= 'f') return c - 'a' + 10;
177
- if (c >= 'A' && c <= 'F') return c - 'A' + 10;
178
- return 0;
176
+ // OpenSCAD's parse_hex_color (ColorUtil.cc): "#rgb", "#rgba", "#rrggbb" or
177
+ // "#rrggbbaa", every character after '#' a hex digit, or nothing at all.
178
+ std::optional<std::array<double, 4>> parseHex(const std::string& hex) {
179
+ const bool shortForm = hex.size() == 4 || hex.size() == 5;
180
+ const bool longForm = hex.size() == 7 || hex.size() == 9;
181
+ if ((!shortForm && !longForm) || hex[0] != '#') return std::nullopt;
182
+ for (size_t i = 1; i < hex.size(); ++i)
183
+ if (!std::isxdigit(static_cast<unsigned char>(hex[i]))) return std::nullopt;
184
+ const size_t stride = shortForm ? 1 : 2;
185
+ const double channelMax = shortForm ? 15.0 : 255.0;
186
+ std::array<double, 4> rgba{0.0, 0.0, 0.0, 1.0};
187
+ for (size_t i = 0; i < (hex.size() - 1) / stride; ++i)
188
+ rgba[i] = std::stoi(hex.substr(1 + i * stride, stride), nullptr, 16) / channelMax;
189
+ return rgba;
179
190
  }
180
191
 
181
192
  } // namespace
182
193
 
183
- std::array<double, 4> cssColor(const std::string& name, double alpha) {
184
- if (!name.empty() && name[0] == '#') {
185
- const std::string h = name.substr(1);
186
- if (h.size() == 6) {
187
- const double r = (hexDigit(h[0]) * 16 + hexDigit(h[1])) / 255.0;
188
- const double g = (hexDigit(h[2]) * 16 + hexDigit(h[3])) / 255.0;
189
- const double b = (hexDigit(h[4]) * 16 + hexDigit(h[5])) / 255.0;
190
- return {r, g, b, alpha};
191
- }
192
- if (h.size() == 3) {
193
- const double r = hexDigit(h[0]) / 15.0;
194
- const double g = hexDigit(h[1]) / 15.0;
195
- const double b = hexDigit(h[2]) / 15.0;
196
- return {r, g, b, alpha};
197
- }
198
- return {1.0, 1.0, 1.0, alpha};
194
+ std::optional<std::array<double, 4>> parseColor(const std::string& text) {
195
+ // OpenSCAD's parse_color: a name first (case-insensitive; "xkcd:" reaches
196
+ // the xkcd survey table, falling back to the CSS table), then hex.
197
+ const std::string name = toLower(text);
198
+ if (name.rfind("xkcd:", 0) == 0) {
199
+ const auto& xk = xkcdColorTable();
200
+ if (auto it = xk.find(name.substr(5)); it != xk.end())
201
+ return std::array<double, 4>{it->second[0] / 255.0, it->second[1] / 255.0, it->second[2] / 255.0, 1.0};
199
202
  }
203
+ // OpenSCAD's own addition to the CSS list: fully transparent black.
204
+ if (name == "transparent") return std::array<double, 4>{0.0, 0.0, 0.0, 0.0};
205
+ if (auto it = colorTable().find(name); it != colorTable().end())
206
+ return std::array<double, 4>{it->second[0], it->second[1], it->second[2], 1.0};
207
+ return parseHex(text);
208
+ }
200
209
 
201
- auto it = colorTable().find(toLower(name));
202
- if (it != colorTable().end()) {
203
- return {it->second[0], it->second[1], it->second[2], alpha};
204
- }
205
- return {1.0, 1.0, 1.0, alpha};
210
+ std::array<double, 4> cssColor(const std::string& name, double alpha) {
211
+ auto c = parseColor(name);
212
+ if (!c) return {1.0, 1.0, 1.0, alpha};
213
+ return {(*c)[0], (*c)[1], (*c)[2], alpha};
206
214
  }
207
215
 
208
216
  } // namespace oscadeval
@@ -104,6 +104,7 @@ void Evaluator::checkDebug(const oscad::ASTNode& node, EvalContext& ctx, bool fo
104
104
  // injecting stops the user never wrote and corrupting lastStmtByDepth_'s
105
105
  // duplicate-collapse state below. Rides the existing null-hook
106
106
  // short-circuit so it costs nothing on the normal path.
107
+ checkCancel(); // every interpreted statement and loop pass comes through here
107
108
  if (measuring_ || !debugHooks_.debugHook) return;
108
109
  const oscad::Position& pos = node.position();
109
110
  const size_t stmtDepth = callStack_.size();
@@ -733,6 +733,9 @@ Evaluator::UserCallHandle Evaluator::enterUserCall(const std::string& name, cons
733
733
  error("Recursion too deep while calling " + std::string(isModule ? "module" : "function") + " '" + name + "'",
734
734
  declNode);
735
735
  }
736
+ if (isModule && moduleCallDepth_ >= kMaxModuleCallDepth) {
737
+ error("Recursion detected calling module '" + name + "'", declNode);
738
+ }
736
739
  UserCallHandle h;
737
740
  h.kind = kind;
738
741
  h.declNode = &declNode;