openscad-cpp-evaluator 1.8.0__tar.gz → 1.10.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 (215) hide show
  1. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/CLAUDE.md +17 -6
  2. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/bindings/module.cpp +26 -6
  4. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +38 -0
  5. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/api.cpp +24 -4
  6. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +3 -0
  7. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/grammar/parser.y +24 -2
  8. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +1 -0
  9. openscad_cpp_evaluator-1.10.0/external/openscad_cpp_parser/tests/test_strict_commas.cpp +117 -0
  10. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/colored_body.hpp +4 -0
  11. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/export.hpp +32 -0
  12. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/pyproject.toml +1 -1
  13. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/python/openscad_cpp_evaluator/__init__.py +22 -4
  14. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/colored_body.cpp +7 -0
  15. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/export.cpp +118 -1
  16. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_export.cpp +182 -0
  17. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_python_bindings.py +77 -0
  18. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tools/cli/cli_lib.cpp +9 -1
  19. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/.github/workflows/ci.yml +0 -0
  20. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/.github/workflows/wheels.yml +0 -0
  21. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/.gitignore +0 -0
  22. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/.gitmodules +0 -0
  23. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/CMakeLists.txt +0 -0
  24. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/LICENSE +0 -0
  25. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/README.md +0 -0
  26. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/bindings/CMakeLists.txt +0 -0
  27. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/bindings/ast_to_py.cpp +0 -0
  28. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/cmake/embed_font.cmake +0 -0
  29. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/examples/CMakeLists.txt +0 -0
  30. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/examples/eval_perf_benchmark.cpp +0 -0
  31. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/examples/manifold_cache_reuse.cpp +0 -0
  32. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/examples/minimal_debugger.cpp +0 -0
  33. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  34. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/.gitignore +0 -0
  35. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  36. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  37. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/README.md +0 -0
  38. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  39. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  41. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  42. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  43. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  44. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  45. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  46. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  47. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  48. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  49. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  50. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  51. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  52. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  53. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  54. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  55. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  56. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  57. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  58. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  59. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  60. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  61. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  62. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  63. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  64. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  65. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  66. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  67. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  68. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  69. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  70. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  71. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  72. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  73. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  74. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  75. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  76. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  77. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  78. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  79. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  80. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  81. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  82. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  83. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  84. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  85. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  86. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  87. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  88. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  89. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  90. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  91. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  92. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  93. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  94. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  95. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  96. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  97. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  98. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  99. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  100. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  101. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  102. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  103. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  104. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  105. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  106. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  107. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  108. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  109. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  110. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  111. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  112. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  113. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  114. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  116. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  117. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  118. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  119. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  120. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  121. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  122. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  123. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  124. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  125. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  126. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  127. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  128. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  129. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/resources/fonts/LICENSE +0 -0
  130. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  131. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/CMakeLists.txt +0 -0
  132. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/booleans.cpp +0 -0
  133. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/builtins.hpp +0 -0
  134. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/call_args.cpp +0 -0
  135. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/color.cpp +0 -0
  136. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/control.cpp +0 -0
  137. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/dxf_dim.cpp +0 -0
  138. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/extrude.cpp +0 -0
  139. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/function_builtins.cpp +0 -0
  140. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/import.cpp +0 -0
  141. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/modifiers.cpp +0 -0
  142. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/primitives_2d.cpp +0 -0
  143. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/primitives_3d.cpp +0 -0
  144. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/registry.cpp +0 -0
  145. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/roof.cpp +0 -0
  146. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/segments.cpp +0 -0
  147. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/surface.cpp +0 -0
  148. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/text.cpp +0 -0
  149. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/topology.cpp +0 -0
  150. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/transforms.cpp +0 -0
  151. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/bytecode_compiler.cpp +0 -0
  152. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/bytecode_vm.cpp +0 -0
  153. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/csg_generate.cpp +0 -0
  154. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/csg_resolve.cpp +0 -0
  155. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/css_colors.cpp +0 -0
  156. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/debug/debug_repl.cpp +0 -0
  157. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/debug_profile.cpp +0 -0
  158. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/eval_context.cpp +0 -0
  159. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/eval_error.cpp +0 -0
  160. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/eval_use.cpp +0 -0
  161. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/expr_eval.cpp +0 -0
  162. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/format_closure.cpp +0 -0
  163. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/import/dxf_import.cpp +0 -0
  164. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/import/mesh_import.cpp +0 -0
  165. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/import/surface_load.cpp +0 -0
  166. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/import/svg_import.cpp +0 -0
  167. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/manifold_cache.cpp +0 -0
  168. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/measure_geometry.cpp +0 -0
  169. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/mesh_check.cpp +0 -0
  170. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/stmt_eval.cpp +0 -0
  171. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/text/font_match.cpp +0 -0
  172. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/text/freetype_font_provider.cpp +0 -0
  173. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/text/text_metrics.cpp +0 -0
  174. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/user_calls.cpp +0 -0
  175. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/utf8.cpp +0 -0
  176. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/value.cpp +0 -0
  177. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/src/zip_stored.cpp +0 -0
  178. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/CMakeLists.txt +0 -0
  179. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_booleans.cpp +0 -0
  180. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_bytecode_compiler.cpp +0 -0
  181. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_children_separate.cpp +0 -0
  182. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_cli.cpp +0 -0
  183. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_control_flow.cpp +0 -0
  184. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_csg_tree.cpp +0 -0
  185. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_debug_hooks.cpp +0 -0
  186. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_dxf_svg_import.cpp +0 -0
  187. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_eval_context.cpp +0 -0
  188. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_expr_eval.cpp +0 -0
  189. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_extrude_roof.cpp +0 -0
  190. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_feature_detection.cpp +0 -0
  191. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_function_builtins.cpp +0 -0
  192. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_helpers.hpp +0 -0
  193. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_import_export.cpp +0 -0
  194. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_include_cache.cpp +0 -0
  195. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_manifold_cache.cpp +0 -0
  196. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_mesh_check.cpp +0 -0
  197. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_multi_color_merge.cpp +0 -0
  198. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_primitives.cpp +0 -0
  199. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_profiling.cpp +0 -0
  200. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_render_expr.cpp +0 -0
  201. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_scope_trail.cpp +0 -0
  202. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_scoping.cpp +0 -0
  203. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_surface.cpp +0 -0
  204. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_tail_calls.cpp +0 -0
  205. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_text.cpp +0 -0
  206. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_transforms.cpp +0 -0
  207. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_unexpected_args.cpp +0 -0
  208. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_use.cpp +0 -0
  209. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_value.cpp +0 -0
  210. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_viewport_params.cpp +0 -0
  211. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tests/test_zip_stored.cpp +0 -0
  212. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tools/cli/CMakeLists.txt +0 -0
  213. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tools/cli/cli_lib.hpp +0 -0
  214. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/tools/cli/main.cpp +0 -0
  215. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.10.0}/uv.lock +0 -0
@@ -916,8 +916,8 @@ grep for `ponytail:`.
916
916
  — skip it and the mesh comes out non-manifold at the seam). Self-contained 2D geometry math here
917
917
  (`dot2`/`len2`/`norm2`, the parabola-canonical-frame rotation in `discretizeArc`) exists nowhere
918
918
  else in this codebase.
919
- - `include/openscad_cpp_evaluator/export.hpp`, `src/export.cpp` — the writers for all eight
920
- formats `exportExtensions()` lists: 3MF, AMF, STL, OBJ, OFF, PLY, VRML, X3D (3MF via
919
+ - `include/openscad_cpp_evaluator/export.hpp`, `src/export.cpp` — the writers for all nine
920
+ formats `exportExtensions()` lists: 3MF, AMF, STL, OBJ, OFF, PLY, SVG, VRML, X3D (3MF via
921
921
  `zip_stored.hpp`'s `writeDeflateZip`). **`exportExtensions()` is the one source of truth for
922
922
  which formats exist** — the Python facade and BelfrySCAD's dialog/CLI all ask it rather than
923
923
  restating the list, because three hand-written copies had already drifted far enough that `.off`
@@ -927,11 +927,22 @@ grep for `ponytail:`.
927
927
  than the face, so an object whose triangles are not all one colour is written as one volume per
928
928
  colour — per-triangle `<color>` is legal in the spec but poorly supported by the slicers that are
929
929
  the audience for it.
930
+ **`writeSvg` is the one 2D writer**, and the only one that reads `ColoredBody::section` rather
931
+ than `.body`: SVG 1.1, millimetres at 1:1, every contour of a body (holes included) a
932
+ `M .. L .. z` subpath of that body's single `<path>`. Model space is Y-up and SVG's is Y-down, so
933
+ the whole point transform is a Y negation; the page is the bounding box padded by **half the
934
+ stroke width** and rounded outward to whole millimetres, which is why the margin is not a
935
+ constant and why `stroke=false` shrinks the document. Both rules are OpenSCAD's, read from its
936
+ `export_svg.cc` — `ExportSvg.MatchesRealOpenscadByteForByte` pins the output against the real
937
+ binary's, vertex order and 6-vertex line wrap included. A 3D body in the list throws
938
+ ("Current top level object is not a 2D object"), as OpenSCAD refuses too: there is no projection
939
+ to fall back on. `ExportOptions::svg` carries OpenSCAD's five `-O export-svg/...` settings.
930
940
  `colored_body.hpp`/`.cpp`'s `toRenderableBodies()` (thin-extrudes any top-level 2D-only result to
931
- a 1e-3-unit-tall `flatPreview` Manifold) must run on a body list before handing it to any of these
932
- writers — none of them look at `ColoredBody::section` at all, only `.body`. `tools/cli/cli_lib.cpp`
933
- is the only current caller; a future GUI/renderer caller needs the same call before passing
934
- results to its own mesh consumer.
941
+ a 1-unit-tall `flatPreview` Manifold) must run on a body list before handing it to any of the
942
+ MESH writers — none of them look at `ColoredBody::section` at all, only `.body`. Not before
943
+ `writeSvg`, which needs the sections that step throws away, and which says so rather than writing
944
+ the slab's outline. `tools/cli/cli_lib.cpp` is the only current caller; a future GUI/renderer
945
+ caller needs the same call before passing results to its own mesh consumer.
935
946
  - `include/openscad_cpp_evaluator/function_builtins.hpp`, `src/builtins/function_builtins.cpp` —
936
947
  the math/string/list/type-check builtin *function* dispatch (`isBuiltinFunctionName`/
937
948
  `evalBuiltinFunction`), a completely separate namespace/table from `dispatch.hpp`'s builtin
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.8.0
3
+ Version: 1.10.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -357,7 +357,8 @@ struct Geometry {
357
357
  // exportModel, with the path/format/warnings marshalling. Releases the GIL:
358
358
  // a large export is seconds of Manifold work with no Python involved.
359
359
  nb::list exportModelPy(const std::string& path, const Geometry& geom, const std::string& format, bool asciiStl,
360
- bool stripSlivers, bool splitComponents) {
360
+ bool stripSlivers, bool splitComponents, bool svgFill, const std::string& svgFillColor,
361
+ bool svgStroke, const std::string& svgStrokeColor, double svgStrokeWidth) {
361
362
  std::vector<std::string> warnings;
362
363
  {
363
364
  nb::gil_scoped_release rel;
@@ -366,6 +367,11 @@ nb::list exportModelPy(const std::string& path, const Geometry& geom, const std:
366
367
  opts.asciiStl = asciiStl;
367
368
  opts.stripSlivers = stripSlivers;
368
369
  opts.splitComponents = splitComponents;
370
+ opts.svg.fill = svgFill;
371
+ opts.svg.fillColor = svgFillColor;
372
+ opts.svg.stroke = svgStroke;
373
+ opts.svg.strokeColor = svgStrokeColor;
374
+ opts.svg.strokeWidth = svgStrokeWidth;
369
375
  warnings = oscadeval::exportModel(path, geom.bodies, opts);
370
376
  }
371
377
  nb::list out;
@@ -375,7 +381,7 @@ nb::list exportModelPy(const std::string& path, const Geometry& geom, const std:
375
381
 
376
382
  nb::object evaluate(const std::string& path, nb::dict viewportParams,
377
383
  std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, bool profile,
378
- bool generate) {
384
+ bool generate, bool strictCommas) {
379
385
  std::unordered_map<std::string, oscadeval::Value> vp = toViewportParams(viewportParams);
380
386
 
381
387
  std::vector<oscadeval::ColoredBody> bodies;
@@ -389,6 +395,12 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
389
395
  nb::gil_scoped_release rel;
390
396
  auto logFn = [&echoes](const std::string& m) { echoes.push_back(m); };
391
397
  try {
398
+ // Constructed on the parsing thread, which is the one whose
399
+ // thread-local mode StrictCommaScope sets -- a flag flipped from
400
+ // Python before the call would sit on the wrong thread the
401
+ // moment a host evaluates off the main one.
402
+ std::optional<oscad::StrictCommaScope> strict;
403
+ if (strictCommas) strict.emplace();
392
404
  oscad::ParsedProgram program = oscad::getProgramFromFile(path);
393
405
  oscadeval::ResolvedUseScopes used = oscadeval::resolveUseScopes(program.nodes, path, logFn);
394
406
  oscadeval::Evaluator ev(logFn, nullptr, manifoldCache, oscadeval::DebugHooks{}, profile);
@@ -787,25 +799,33 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
787
799
 
788
800
  m.def("export_model", &exportModelPy, nb::arg("path"), nb::arg("geometry"), nb::arg("format") = std::string(),
789
801
  nb::arg("ascii_stl") = false, nb::arg("strip_slivers") = true,
790
- nb::arg("split_components") = false,
802
+ nb::arg("split_components") = false, nb::arg("svg_fill") = false,
803
+ nb::arg("svg_fill_color") = std::string("white"), nb::arg("svg_stroke") = true,
804
+ nb::arg("svg_stroke_color") = std::string("black"), nb::arg("svg_stroke_width") = 0.35,
791
805
  "Write `geometry` to `path`, format taken from the extension unless `format` says otherwise. "
792
806
  "Returns the warnings to surface (open shells, mesh problems, slivers removed) rather than "
793
807
  "logging them. Raises RuntimeError when there is no geometry, the format is unknown, or the "
794
808
  "file cannot be opened.\n\n"
795
809
  "`split_components` gives every disconnected piece its own object in the multi-object formats "
796
810
  "(3MF, AMF, OBJ, PLY, VRML, X3D). Off by default, matching OpenSCAD, which writes one object "
797
- "per colour however many pieces it is in.");
811
+ "per colour however many pieces it is in.\n\n"
812
+ "The svg_* arguments are OpenSCAD's -O export-svg/... set and apply to .svg only. "
813
+ "svg_stroke/svg_stroke_width also pad the page, so they are not purely cosmetic. An .svg "
814
+ "export needs an all-2D model and raises otherwise, as OpenSCAD does.");
798
815
 
799
816
  m.def("export_extensions", []() { return oscadeval::exportExtensions(); },
800
817
  "The file extensions export_model understands, dot-prefixed. Read from the C++ writer table "
801
818
  "rather than restated here, so the Python side cannot drift from what can actually be written.");
802
819
 
803
820
  m.def("evaluate", &evaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("manifold_cache") = nullptr,
804
- nb::arg("profile") = false, nb::arg("generate") = true,
821
+ nb::arg("profile") = false, nb::arg("generate") = true, nb::arg("strict_commas") = false,
805
822
  "Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit).\n"
806
823
  "generate=False stops after the resolve pass: the script runs and reports everything "
807
824
  "it normally would, but no Manifold geometry is built, and neither bodies nor csg_tree "
808
- "are populated.");
825
+ "are populated.\n\n"
826
+ "strict_commas=True makes a trailing comma in a call argument list or a let/for "
827
+ "assignment list a syntax error, as OpenSCAD 2021.01 did. List literals and parameter "
828
+ "declarations keep theirs, which 2021.01 accepted too.");
809
829
  m.def("parse_decls", &parseDecls, nb::arg("path"),
810
830
  "Parse a .scad file; return top-level declaration (namespace, name, start, end, line, column, origin) tuples.");
811
831
  m.def("strip_slivers", &stripSliversPy, nb::arg("verts"), nb::arg("tris"),
@@ -60,6 +60,44 @@ std::unique_ptr<Scope> buildScopes(const std::vector<ASTNode*>& ast);
60
60
  std::unique_ptr<Scope> buildScopesInto(const std::vector<std::unique_ptr<ASTNode>>& ast, ScopeTable& table);
61
61
  std::unique_ptr<Scope> buildScopesInto(const std::vector<ASTNode*>& ast, ScopeTable& table);
62
62
 
63
+ // Makes a trailing comma in a CALL ARGUMENT LIST a syntax error for as long
64
+ // as it is in scope, matching OpenSCAD 2021.01:
65
+ //
66
+ // cube(1,); // error while this is in scope
67
+ // echo(let(x = 1, y = 2,) x) // error -- let() parses as a call
68
+ // a = [2, 4,]; // still fine: 2021.01 accepts it too
69
+ // module m(a, b,) {} // still fine, same reason
70
+ //
71
+ // One production (`arguments`) feeds every call form -- module
72
+ // instantiation, function calls, echo, assert, render and let -- so this is
73
+ // one rule, not a family of them. Parameter lists and list literals are
74
+ // deliberately untouched: 2021.01 accepts a trailing comma in both, and
75
+ // rejecting them would fail files it loads happily.
76
+ //
77
+ // A scope object rather than a parameter on all nine parse entry points:
78
+ // this is a parse-wide mode, the same shape ParseNumberingScope already
79
+ // uses, and threading a bool through getASTFromString/getASTFromFile/
80
+ // getASTFromLibraryFile/getProgramFromFile and their helpers would touch
81
+ // every one of them to say the same thing. Nests and restores on scope
82
+ // exit, exceptions included; thread-local, so one thread parsing strictly
83
+ // cannot change what another thread sees.
84
+ //
85
+ // getProgramFromFile()'s cache keys on this too -- a strict parse must not
86
+ // be handed a tree an earlier lenient parse of the same file left behind.
87
+ class StrictCommaScope {
88
+ public:
89
+ StrictCommaScope();
90
+ ~StrictCommaScope();
91
+ StrictCommaScope(const StrictCommaScope&) = delete;
92
+ StrictCommaScope& operator=(const StrictCommaScope&) = delete;
93
+
94
+ private:
95
+ bool previous_;
96
+ };
97
+
98
+ // Whether a StrictCommaScope is currently in effect on this thread.
99
+ bool strictCommasEnabled();
100
+
63
101
  // Parses `code`. Throws ParseError (with the full caret diagnostic) on a
64
102
  // syntax error.
65
103
  //
@@ -89,6 +89,17 @@ std::string formatSyntaxError(const ParserDriver& driver, const std::string& cod
89
89
 
90
90
  } // namespace
91
91
 
92
+ namespace {
93
+ // See StrictCommaScope (api.hpp). Thread-local so a strict parse on one
94
+ // thread cannot change what another thread is parsing.
95
+ thread_local bool g_strictCommas = false;
96
+ } // namespace
97
+
98
+ StrictCommaScope::StrictCommaScope() : previous_(g_strictCommas) { g_strictCommas = true; }
99
+ StrictCommaScope::~StrictCommaScope() { g_strictCommas = previous_; }
100
+
101
+ bool strictCommasEnabled() { return g_strictCommas; }
102
+
92
103
  std::vector<std::unique_ptr<ASTNode>> parseAst(const std::string& code, const std::string& origin, SourceMap* sourceMap) {
93
104
  // Stamps every node this parse builds with one treeId and a dense
94
105
  // slot, so a ScopeTable can address it without the node carrying a
@@ -96,6 +107,7 @@ std::vector<std::unique_ptr<ASTNode>> parseAst(const std::string& code, const st
96
107
  ParseNumberingScope numbering;
97
108
 
98
109
  ParserDriver driver(origin);
110
+ driver.strictCommas = g_strictCommas;
99
111
  lexerBeginString(code);
100
112
  yy::parser parser(driver);
101
113
  int rc = parser.parse();
@@ -258,7 +270,8 @@ namespace {
258
270
  using FileAst = std::vector<std::unique_ptr<ASTNode>>;
259
271
  using FileAstPtr = std::shared_ptr<const FileAst>;
260
272
 
261
- // One entry per (file, comments) -- keyed by PATH, with the content stamp
273
+ // One entry per (file, comments, strict-commas) -- keyed by PATH, with the
274
+ // content stamp
262
275
  // stored beside the tree rather than in the key. A stale stamp REPLACES the
263
276
  // entry instead of adding a second one: an editor re-renders on every save,
264
277
  // and a stamp-in-the-key cache would keep a full copy of every version the
@@ -266,11 +279,18 @@ using FileAstPtr = std::shared_ptr<const FileAst>;
266
279
  struct CacheKey {
267
280
  std::string path;
268
281
  bool comments;
269
- bool operator==(const CacheKey& o) const { return comments == o.comments && path == o.path; }
282
+ // Part of the key, not incidental: the same file parses differently
283
+ // under StrictCommaScope, so a strict parse must not be served a tree an
284
+ // earlier lenient parse of it left here (and vice versa).
285
+ bool strictCommas;
286
+ bool operator==(const CacheKey& o) const {
287
+ return comments == o.comments && strictCommas == o.strictCommas && path == o.path;
288
+ }
270
289
  };
271
290
  struct CacheKeyHash {
272
291
  size_t operator()(const CacheKey& k) const {
273
- return std::hash<std::string>{}(k.path) ^ (k.comments ? 0x5bf03635U : 0U);
292
+ return std::hash<std::string>{}(k.path) ^ (k.comments ? 0x5bf03635U : 0U)
293
+ ^ (k.strictCommas ? 0x9e3779b9U : 0U);
274
294
  }
275
295
  };
276
296
  struct CacheEntry {
@@ -290,7 +310,7 @@ FileAstPtr parseFileShared(const std::string& absPath, bool includeComments) {
290
310
  const auto written = fs::last_write_time(absPath, ec);
291
311
  const std::int64_t stampMtime = ec ? 0 : static_cast<std::int64_t>(written.time_since_epoch().count());
292
312
 
293
- const CacheKey key{absPath, includeComments};
313
+ const CacheKey key{absPath, includeComments, g_strictCommas};
294
314
  {
295
315
  std::lock_guard<std::mutex> lock(g_astCacheMutex);
296
316
  auto it = g_astCache.find(key);
@@ -39,6 +39,9 @@ public:
39
39
  std::string origin_;
40
40
  NodeList result;
41
41
  bool hadError = false;
42
+ // Copied from the ambient StrictCommaScope at parse start (api.cpp), so
43
+ // one parse's mode cannot change under it mid-run.
44
+ bool strictCommas = false;
42
45
  int errorLine = 0;
43
46
  int errorColumn = 0;
44
47
  int errorOffset = 0;
@@ -247,7 +247,18 @@ parameter:
247
247
  arguments:
248
248
  %empty { $$ = NodeList{}; }
249
249
  | argument_seq { $$ = std::move($1); }
250
- | argument_seq "," { $$ = std::move($1); }
250
+ // The trailing comma OpenSCAD 2021.01 rejected. Still parsed, then
251
+ // reported -- @2 is the comma itself, so the caret lands on it rather
252
+ // than on the whole argument list, which a %expect-style grammar split
253
+ // could not manage. This one production feeds every call form (module
254
+ // instantiation, function calls, echo, assert, render, and let), which
255
+ // is why the whole feature is one rule. See StrictCommaScope (api.hpp).
256
+ | argument_seq "," {
257
+ if (driver.strictCommas) {
258
+ driver.reportError(@2, "trailing comma in argument list");
259
+ }
260
+ $$ = std::move($1);
261
+ }
251
262
  ;
252
263
 
253
264
  argument_seq:
@@ -263,7 +274,18 @@ argument:
263
274
  assignments_expr:
264
275
  %empty { $$ = NodeList{}; }
265
276
  | assignment_expr_seq { $$ = std::move($1); }
266
- | assignment_expr_seq "," { $$ = std::move($1); }
277
+ // 2021.01 rejected this one too. `let` does NOT go through `arguments`
278
+ // in this grammar -- it has its own assignment list, shared with `for`
279
+ // and `intersection_for` -- so the feature is two productions, not the
280
+ // one it looks like. 2021.01 rejects a trailing comma in all four:
281
+ // let(x=1,), for(i=[0:2],), intersection_for(i=[0:1],) and the list
282
+ // comprehension forms of for. See StrictCommaScope (api.hpp).
283
+ | assignment_expr_seq "," {
284
+ if (driver.strictCommas) {
285
+ driver.reportError(@2, "trailing comma in assignment list");
286
+ }
287
+ $$ = std::move($1);
288
+ }
267
289
  ;
268
290
 
269
291
  assignment_expr_seq:
@@ -26,6 +26,7 @@ add_executable(oscad_tests
26
26
  test_ast_generation.cpp
27
27
  test_node_str.cpp
28
28
  test_render_expression.cpp
29
+ test_strict_commas.cpp
29
30
  )
30
31
  target_link_libraries(oscad_tests PRIVATE openscad_cpp_parser GTest::gtest_main)
31
32
 
@@ -0,0 +1,117 @@
1
+ // StrictCommaScope: a trailing comma in a call argument list is an error,
2
+ // matching OpenSCAD 2021.01 (BelfrySCAD issue #362).
3
+ //
4
+ // The scope is deliberately narrow. Measured against 2021.01 itself, it
5
+ // rejected a trailing comma in CALL ARGUMENTS and accepted one everywhere
6
+ // else -- list literals, list comprehensions, and module/function parameter
7
+ // declarations all take one, in 2021.01 as in every version since. Rejecting
8
+ // those would fail files 2021.01 loads happily, so they are left alone and
9
+ // this file says so case by case.
10
+ #include "test_helpers.hpp"
11
+
12
+ #include <gtest/gtest.h>
13
+
14
+ using namespace oscad;
15
+
16
+ namespace {
17
+
18
+ // Two productions carry it: `arguments` (every call form) and
19
+ // `assignments_expr` (let/for/intersection_for). `let` is NOT a call in this
20
+ // grammar -- it has its own assignment list -- which the first draft of this
21
+ // feature got wrong and this test caught.
22
+ const char* const kRejected[] = {
23
+ "cube(1,);", // module instantiation
24
+ "translate([0,0,0],) cube(1);", // ...with a child
25
+ "function g(x) = x; y = g(1,);", // user function call
26
+ "y = max(1, 2,);", // builtin function call
27
+ "y = str(\"a\", \"b\",);",
28
+ "echo(1, 2,);",
29
+ "assert(true, \"m\",);",
30
+ // assignments_expr, not arguments
31
+ "y = let(x = 1, y = 2,) x + y;",
32
+ "let(x = 1,) cube(x);",
33
+ "for (i = [0:2],) cube(i);",
34
+ "intersection_for (i = [0:1],) cube(1);",
35
+ "a = [for (i = [0:1],) i];",
36
+ };
37
+
38
+ // Accepted by 2021.01, so accepted here even under the scope.
39
+ const char* const kAccepted[] = {
40
+ "a = [2, 4,];", // list literal
41
+ "a = [for (i = [0:2]) i,];", // list comprehension
42
+ "a = [each [1, 2],];",
43
+ "module m(a, b,) {} m(1, 2);", // module parameter declaration
44
+ "function f(a, b,) = a + b;", // function parameter declaration
45
+ "cube(1);", // no trailing comma at all
46
+ "y = max(1, 2);",
47
+ };
48
+
49
+ } // namespace
50
+
51
+ TEST(StrictCommas, CallArgumentTrailingCommaIsAnErrorInScope) {
52
+ StrictCommaScope strict;
53
+ for (const char* src : kRejected) {
54
+ EXPECT_THROW(parseSrc(src), ParseError) << src;
55
+ }
56
+ }
57
+
58
+ TEST(StrictCommas, EverythingElseKeepsItsTrailingComma) {
59
+ StrictCommaScope strict;
60
+ for (const char* src : kAccepted) {
61
+ EXPECT_NO_THROW(parseSrc(src)) << src;
62
+ }
63
+ }
64
+
65
+ TEST(StrictCommas, OffByDefault) {
66
+ // The whole set parses without the scope -- this is opt-in, and every
67
+ // existing caller must be unaffected.
68
+ for (const char* src : kRejected) {
69
+ EXPECT_NO_THROW(parseSrc(src)) << src;
70
+ }
71
+ }
72
+
73
+ TEST(StrictCommas, ScopeNestsAndRestores) {
74
+ EXPECT_FALSE(strictCommasEnabled());
75
+ {
76
+ StrictCommaScope outer;
77
+ EXPECT_TRUE(strictCommasEnabled());
78
+ {
79
+ StrictCommaScope inner;
80
+ EXPECT_TRUE(strictCommasEnabled());
81
+ }
82
+ EXPECT_TRUE(strictCommasEnabled()) << "an inner scope must not switch it off";
83
+ }
84
+ EXPECT_FALSE(strictCommasEnabled());
85
+ }
86
+
87
+ TEST(StrictCommas, RestoresWhenAParseThrows) {
88
+ try {
89
+ StrictCommaScope strict;
90
+ parseSrc("cube(1,);");
91
+ FAIL() << "expected ParseError";
92
+ } catch (const ParseError&) {
93
+ }
94
+ EXPECT_FALSE(strictCommasEnabled()) << "the scope must unwind with the exception";
95
+ }
96
+
97
+ TEST(StrictCommas, TheErrorPointsAtTheComma) {
98
+ StrictCommaScope strict;
99
+ try {
100
+ parseSrc("cube(1,);");
101
+ FAIL() << "expected ParseError";
102
+ } catch (const ParseError& e) {
103
+ const std::string what = e.what();
104
+ EXPECT_NE(what.find("trailing comma in argument list"), std::string::npos) << what;
105
+ }
106
+ }
107
+
108
+ TEST(StrictCommas, AnAssignmentListSaysSo) {
109
+ StrictCommaScope strict;
110
+ try {
111
+ parseSrc("y = let(x = 1,) x;");
112
+ FAIL() << "expected ParseError";
113
+ } catch (const ParseError& e) {
114
+ const std::string what = e.what();
115
+ EXPECT_NE(what.find("trailing comma in assignment list"), std::string::npos) << what;
116
+ }
117
+ }
@@ -16,6 +16,10 @@ enum class BodyRole { Normal, Highlight, Background, ShowOnly };
16
16
  // A single piece of evaluated geometry: exactly one of `body` (3D) or
17
17
  // `section` (2D) is set, never both -- mirrors the Python reference's
18
18
  // ColoredBody (body: Optional[Manifold] XOR section: Optional[CrossSection]).
19
+ // ONE exception, and it is deliberate: toRenderableBodies() leaves the
20
+ // `section` in place on the `flatPreview` body it extrudes, so a 2D writer
21
+ // downstream still has the contours. `flatPreview` is the flag that says
22
+ // so; nothing else sets both.
19
23
  // `role`/`triColors` are later-phase fields (modifier tagging -- Phase 3;
20
24
  // multi-color CSG merges -- Phase 3/8) included now so the struct shape
21
25
  // doesn't need reworking when those land.
@@ -127,6 +127,36 @@ void writeOff(const std::string& path, const std::vector<ColoredBody>& bodies);
127
127
  // no geometry to export.
128
128
  void writeThreeMf(const std::string& path, const std::vector<ExportObject>& objects);
129
129
 
130
+ // -- 2D ------------------------------------------------------------------
131
+
132
+ // Options for the SVG writer, named and defaulted after OpenSCAD's own
133
+ // `-O export-svg/...` set. `stroke`/`strokeWidth` are not purely cosmetic:
134
+ // half the stroke width pads the page, so turning the stroke off shrinks
135
+ // the document.
136
+ struct ExportSvgOptions {
137
+ bool fill = false;
138
+ std::string fillColor = "white";
139
+ bool stroke = true;
140
+ std::string strokeColor = "black";
141
+ double strokeWidth = 0.35;
142
+ };
143
+
144
+ // SVG 1.1 of a 2D-only model, in millimetres at 1:1 -- so a print of it
145
+ // measures what the script says. Every contour of a body, holes included,
146
+ // becomes a `M .. L .. z` subpath of that body's single `<path>`.
147
+ //
148
+ // Model space is Y-up and SVG's is Y-down, so the whole point transform is
149
+ // a Y negation. The page is the bounding box padded by half the stroke
150
+ // width and rounded OUTWARD to whole millimetres, which is why the margin
151
+ // is not a constant (0 to just under 1mm per side, depending where the
152
+ // bounds fall). Both rules are OpenSCAD's, read from its export_svg.cc and
153
+ // checked against its binary.
154
+ //
155
+ // Throws std::runtime_error if any top-level body is 3D -- OpenSCAD's rule,
156
+ // and the only honest answer, since there is no projection to fall back on.
157
+ void writeSvg(const std::string& path, const std::vector<ColoredBody>& bodies,
158
+ const ExportSvgOptions& opts = {});
159
+
130
160
  // -- one entry point ------------------------------------------------------
131
161
 
132
162
  struct ExportOptions {
@@ -144,6 +174,8 @@ struct ExportOptions {
144
174
  // pieces it is in; see that function's own comment for why the default
145
175
  // moved here.
146
176
  bool splitComponents = false;
177
+ // .svg only.
178
+ ExportSvgOptions svg;
147
179
  };
148
180
 
149
181
  // Writes `bodies` to `path`, choosing the writer from the extension (or
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.8.0"
7
+ version = "1.10.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -292,7 +292,10 @@ def strip_slivers(verts, tris):
292
292
 
293
293
 
294
294
  def export_model(path: str, geometry, format: str = "", ascii_stl: bool = False,
295
- strip_slivers: bool = True, split_components: bool = False) -> list:
295
+ strip_slivers: bool = True, split_components: bool = False,
296
+ svg_fill: bool = False, svg_fill_color: str = "white",
297
+ svg_stroke: bool = True, svg_stroke_color: str = "black",
298
+ svg_stroke_width: float = 0.35) -> list:
296
299
  """Write `geometry` to `path`. Returns the warnings to surface.
297
300
 
298
301
  `geometry` is the handle an Evaluator stashes on itself as
@@ -312,10 +315,17 @@ def export_model(path: str, geometry, format: str = "", ascii_stl: bool = False,
312
315
 
313
316
  `split_components` gives every disconnected piece its own object in the
314
317
  multi-object formats. Off by default, which is what OpenSCAD writes.
318
+
319
+ The `svg_*` arguments are OpenSCAD's `-O export-svg/...` set and apply
320
+ to `.svg` only, which writes a 2D model at 1:1 in millimetres.
321
+ `svg_stroke`/`svg_stroke_width` also pad the page, so they are not
322
+ purely cosmetic. An `.svg` export of anything 3D raises, as OpenSCAD's
323
+ does -- there is no projection to fall back on.
315
324
  """
316
325
  try:
317
326
  return list(_ext.export_model(path, geometry, format, ascii_stl, strip_slivers,
318
- split_components))
327
+ split_components, svg_fill, svg_fill_color,
328
+ svg_stroke, svg_stroke_color, svg_stroke_width))
319
329
  except Exception as e:
320
330
  raise EvalError(str(e)) from e
321
331
 
@@ -497,7 +507,7 @@ class Evaluator:
497
507
  self.dyn_explicit = set()
498
508
 
499
509
  def evaluate(self, source_path: str, viewport_params: Optional[dict] = None,
500
- generate: bool = True):
510
+ generate: bool = True, strict_commas: bool = False):
501
511
  """Run `source_path` and return (bodies, id_to_node).
502
512
 
503
513
  `generate=False` stops after the resolve pass. The script still runs
@@ -508,6 +518,13 @@ class Evaluator:
508
518
  the geometry without skipping the language.
509
519
 
510
520
  Ignored on the debugger path, which has no geometry handle anyway.
521
+
522
+ `strict_commas=True` makes a trailing comma in a call argument list
523
+ or a let/for assignment list a syntax error, as OpenSCAD 2021.01 did
524
+ -- `cube(1,)` and `let(x = 1,)`, but NOT `[2, 4,]` or
525
+ `module m(a, b,)`, which 2021.01 accepted. For checking a script
526
+ against that version; off, and no divergence from current OpenSCAD,
527
+ otherwise. Also ignored on the debugger path.
511
528
  """
512
529
  vp = viewport_params or {}
513
530
  try:
@@ -528,7 +545,8 @@ class Evaluator:
528
545
  else:
529
546
  (body_dicts, echoes, id_spans, csg_tree, profile_result, dyn,
530
547
  dyn_explicit, geometry) = _ext.evaluate(
531
- source_path, vp, self._manifold_cache, self._profile, generate)
548
+ source_path, vp, self._manifold_cache, self._profile, generate,
549
+ strict_commas)
532
550
  # The evaluated bodies, still on the C++ side. Stashed like
533
551
  # csg_tree/profile_result rather than returned, so
534
552
  # evaluate()'s own 2-tuple result is unchanged -- callers
@@ -43,6 +43,13 @@ std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodi
43
43
  flat.body = flat.body->Transform(cb.sectionXform);
44
44
  flat.color = cb.color;
45
45
  flat.flatPreview = true;
46
+ // Carried, not consumed: the 2D writers (writeSvg) need the
47
+ // contours, and every caller that exports goes through this
48
+ // conversion first -- the Python binding hands ONE body list to
49
+ // both the renderer and export. Dropping it here made an SVG
50
+ // export of any 2D script impossible from Python.
51
+ flat.section = cb.section;
52
+ flat.sectionXform = cb.sectionXform;
46
53
  flat.role = cb.role;
47
54
  out.push_back(std::move(flat));
48
55
  } else {