openscad-cpp-evaluator 1.31.0__tar.gz → 1.33.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.31.0 → openscad_cpp_evaluator-1.33.0}/.github/workflows/wheels.yml +9 -0
  2. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/CLAUDE.md +26 -2
  3. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/PKG-INFO +1 -1
  4. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/bindings/module.cpp +22 -3
  5. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/export.hpp +41 -0
  6. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/pyproject.toml +1 -1
  7. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/python/openscad_cpp_evaluator/__init__.py +8 -2
  8. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/export.cpp +266 -3
  9. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_export.cpp +243 -0
  10. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_python_bindings.py +121 -26
  11. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/uv.lock +1 -1
  12. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/.github/workflows/ci.yml +0 -0
  13. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/.gitignore +0 -0
  14. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/.gitmodules +0 -0
  15. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/CMakeLists.txt +0 -0
  16. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/LICENSE +0 -0
  17. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/README.md +0 -0
  18. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/bindings/CMakeLists.txt +0 -0
  19. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/bindings/ast_to_py.cpp +0 -0
  20. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/cmake/embed_font.cmake +0 -0
  21. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/examples/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/examples/eval_perf_benchmark.cpp +0 -0
  23. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/examples/manifold_cache_reuse.cpp +0 -0
  24. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/examples/minimal_debugger.cpp +0 -0
  25. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  26. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/.gitignore +0 -0
  27. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  28. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/README.md +0 -0
  30. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  31. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  32. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  33. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  34. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  35. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  36. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  37. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  38. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  39. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  41. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  42. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  43. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  44. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  45. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  46. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  47. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  48. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  49. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  50. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  51. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  52. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  53. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  54. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  55. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  56. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  57. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  58. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  59. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  60. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  61. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  62. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  63. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  64. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  65. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  66. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  67. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  68. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  69. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  70. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  71. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  72. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  73. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  74. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  75. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  76. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  77. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  78. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  79. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  80. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  81. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  82. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  83. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  84. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  85. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  86. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  87. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  88. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  89. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  90. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  91. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  92. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  93. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  94. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  95. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  96. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  97. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  98. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  99. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  100. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  101. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  102. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  103. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  104. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  105. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  106. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  107. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  108. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  109. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  110. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  111. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  112. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  113. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  114. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  116. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  117. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  118. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  119. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  120. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  121. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  122. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  123. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  124. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  125. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  126. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  127. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  128. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  129. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  130. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  131. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/resources/fonts/LICENSE +0 -0
  132. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  133. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  134. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  135. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  136. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/CMakeLists.txt +0 -0
  137. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/booleans.cpp +0 -0
  138. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/builtins.hpp +0 -0
  139. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/call_args.cpp +0 -0
  140. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/color.cpp +0 -0
  141. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/control.cpp +0 -0
  142. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/dxf_dim.cpp +0 -0
  143. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/extrude.cpp +0 -0
  144. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/function_builtins.cpp +0 -0
  145. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/import.cpp +0 -0
  146. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/modifiers.cpp +0 -0
  147. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/primitives_2d.cpp +0 -0
  148. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/primitives_3d.cpp +0 -0
  149. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/registry.cpp +0 -0
  150. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/roof.cpp +0 -0
  151. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/segments.cpp +0 -0
  152. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/surface.cpp +0 -0
  153. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/text.cpp +0 -0
  154. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/topology.cpp +0 -0
  155. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/builtins/transforms.cpp +0 -0
  156. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/bytecode_compiler.cpp +0 -0
  157. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/bytecode_vm.cpp +0 -0
  158. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/colored_body.cpp +0 -0
  159. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/coverage.cpp +0 -0
  160. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/csg_generate.cpp +0 -0
  161. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/csg_resolve.cpp +0 -0
  162. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/css_colors.cpp +0 -0
  163. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/debug/debug_repl.cpp +0 -0
  164. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/debug_profile.cpp +0 -0
  165. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/eval_context.cpp +0 -0
  166. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/eval_error.cpp +0 -0
  167. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/eval_use.cpp +0 -0
  168. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/expr_eval.cpp +0 -0
  169. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/format_closure.cpp +0 -0
  170. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/import/dxf_import.cpp +0 -0
  171. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/import/mesh_import.cpp +0 -0
  172. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/import/surface_load.cpp +0 -0
  173. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/import/svg_import.cpp +0 -0
  174. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/manifold_cache.cpp +0 -0
  175. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/measure_geometry.cpp +0 -0
  176. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/mesh_check.cpp +0 -0
  177. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/stmt_eval.cpp +0 -0
  178. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/text/font_match.cpp +0 -0
  179. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/text/freetype_font_provider.cpp +0 -0
  180. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/text/text_metrics.cpp +0 -0
  181. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/user_calls.cpp +0 -0
  182. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/utf8.cpp +0 -0
  183. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/value.cpp +0 -0
  184. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/xkcd_colors.cpp +0 -0
  185. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/xkcd_colors.hpp +0 -0
  186. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/src/zip_stored.cpp +0 -0
  187. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/CMakeLists.txt +0 -0
  188. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_booleans.cpp +0 -0
  189. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_bytecode_compiler.cpp +0 -0
  190. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_children_separate.cpp +0 -0
  191. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_cli.cpp +0 -0
  192. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_control_flow.cpp +0 -0
  193. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_coverage.cpp +0 -0
  194. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_csg_tree.cpp +0 -0
  195. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_debug_hooks.cpp +0 -0
  196. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_discretizer.cpp +0 -0
  197. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_dxf_svg_import.cpp +0 -0
  198. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_eval_context.cpp +0 -0
  199. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_expr_eval.cpp +0 -0
  200. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_extrude_roof.cpp +0 -0
  201. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_feature_detection.cpp +0 -0
  202. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_function_builtins.cpp +0 -0
  203. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_helpers.hpp +0 -0
  204. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_import_export.cpp +0 -0
  205. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_include_cache.cpp +0 -0
  206. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_manifold_cache.cpp +0 -0
  207. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_mesh_check.cpp +0 -0
  208. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_multi_color_merge.cpp +0 -0
  209. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_primitives.cpp +0 -0
  210. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_profiling.cpp +0 -0
  211. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_render_expr.cpp +0 -0
  212. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_scope_trail.cpp +0 -0
  213. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_scoping.cpp +0 -0
  214. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_surface.cpp +0 -0
  215. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_tail_calls.cpp +0 -0
  216. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_text.cpp +0 -0
  217. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_transforms.cpp +0 -0
  218. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_unexpected_args.cpp +0 -0
  219. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_use.cpp +0 -0
  220. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_value.cpp +0 -0
  221. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_viewport_params.cpp +0 -0
  222. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tests/test_zip_stored.cpp +0 -0
  223. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tools/cli/CMakeLists.txt +0 -0
  224. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tools/cli/cli_lib.cpp +0 -0
  225. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tools/cli/cli_lib.hpp +0 -0
  226. {openscad_cpp_evaluator-1.31.0 → openscad_cpp_evaluator-1.33.0}/tools/cli/main.cpp +0 -0
@@ -119,6 +119,9 @@ jobs:
119
119
  # C:\msys64 (not on PATH); its bison/flex satisfy the parser
120
120
  # submodule's grammar version requirement.
121
121
  CIBW_BEFORE_ALL_WINDOWS: 'C:\msys64\usr\bin\bash.exe -lc "pacman -Sy --noconfirm bison flex"'
122
+ # The binding tests run under pytest (the file's own entry point
123
+ # hands over to it): most of them take tmp_path or use pytest.raises.
124
+ CIBW_TEST_REQUIRES: "pytest"
122
125
  CIBW_TEST_COMMAND: "python {package}/tests/test_python_bindings.py"
123
126
 
124
127
  - uses: actions/upload-artifact@v7
@@ -128,6 +131,9 @@ jobs:
128
131
 
129
132
  publish-testpypi:
130
133
  needs: [sdist, build_wheels]
134
+ # Tags only: a manual dispatch builds and tests every wheel, and must
135
+ # never publish whatever branch it was run on.
136
+ if: startsWith(github.ref, 'refs/tags/v')
131
137
  runs-on: ubuntu-latest
132
138
  environment: testpypi
133
139
  steps:
@@ -164,6 +170,9 @@ jobs:
164
170
 
165
171
  publish-pypi:
166
172
  needs: [publish-testpypi]
173
+ # Tags only: a manual dispatch builds and tests every wheel, and must
174
+ # never publish whatever branch it was run on.
175
+ if: startsWith(github.ref, 'refs/tags/v')
167
176
  runs-on: ubuntu-latest
168
177
  # The "pypi" environment carries no protection rules, so a tag runs
169
178
  # straight through TestPyPI to PyPI with nothing to stop it -- and a
@@ -1091,8 +1091,8 @@ grep for `ponytail:`.
1091
1091
  — skip it and the mesh comes out non-manifold at the seam). Self-contained 2D geometry math here
1092
1092
  (`dot2`/`len2`/`norm2`, the parabola-canonical-frame rotation in `discretizeArc`) exists nowhere
1093
1093
  else in this codebase.
1094
- - `include/openscad_cpp_evaluator/export.hpp`, `src/export.cpp` — the writers for all ten
1095
- formats `exportExtensions()` lists: 3MF, AMF, PDF, STL, OBJ, OFF, PLY, SVG, VRML, X3D (3MF via
1094
+ - `include/openscad_cpp_evaluator/export.hpp`, `src/export.cpp` — the writers for all twelve
1095
+ formats `exportExtensions()` lists: 3MF, AMF, DXF, PDF, STL, OBJ, OFF, PLY, POV, SVG, VRML, X3D (3MF via
1096
1096
  `zip_stored.hpp`'s `writeDeflateZip`). **`exportExtensions()` is the one source of truth for
1097
1097
  which formats exist** — the Python facade and BelfrySCAD's dialog/CLI all ask it rather than
1098
1098
  restating the list, because three hand-written copies had already drifted far enough that `.off`
@@ -1120,6 +1120,30 @@ grep for `ponytail:`.
1120
1120
  binary's, vertex order and 6-vertex line wrap included. A 3D body in the list throws
1121
1121
  ("Current top level object is not a 2D object"), as OpenSCAD refuses too: there is no projection
1122
1122
  to fall back on. `ExportOptions::svg` carries OpenSCAD's five `-O export-svg/...` settings.
1123
+ **`writePov` writes POV-Ray, as `mesh2`** — one per object `splitBodiesForExport` produces, with
1124
+ a welded vertex list, a `texture_list` of the object's colours and a texture index per triangle
1125
+ (so per-triangle colour needs no unwelding, unlike PLY), and `inside_vector` so POV-Ray treats it
1126
+ as a SOLID (the `ior 1.32` interior refracts; CSG works). OpenSCAD's `export_pov.cc` writes every
1127
+ face as its own flat `polygon` object instead. The rest of the scene is OpenSCAD's: header,
1128
+ MATERIAL finish, 27 lights around the bounding box, radiosity from `rad_def.inc`, colour as
1129
+ `rgbf` with filter = 1 − alpha. `ExportPovOptions::camera` ($vpt, $vpr, distance, fov; Python
1130
+ `pov_camera`, 8 numbers) writes OpenSCAD's camera block with **two fixes, both found by rendering
1131
+ in POV-Ray 3.7 and comparing with BelfrySCAD's PNG of the same camera** (they now agree to a
1132
+ pixel): `rotate` comes before `translate`, since the eye orbits `$vpt` (OpenSCAD translates first
1133
+ and frames the wrong place when a view both pans and turns); and the viewport's **vertical** fov
1134
+ becomes POV-Ray's **horizontal** `angle` through an expression on `image_width/image_height`
1135
+ (OpenSCAD writes it across unchanged, zooming its renders in by the aspect ratio). No camera frames
1136
+ the bounding box, as OpenSCAD does without one.
1137
+ **`writeDxf` is the third 2D writer**, also on `collect2d()`: OpenSCAD's own `export_dxf.cc`
1138
+ layout verbatim (AC1006 header with the extents, one CONTINUOUS linetype, layer 0, every contour
1139
+ a closed `LWPOLYLINE` on layer 0 with holes as ordinary contours; 1- and 2-vertex contours as
1140
+ `POINT`/`LINE`), pinned byte-for-byte by `ExportDxf.MatchesRealOpenscadByteForByte`. Two
1141
+ deliberate differences: coordinates are the **shortest text that round-trips the double**
1142
+ (`dxfNum`), not OpenSCAD's 6 significant digits -- a DXF feeds a cutter, and its circles do not
1143
+ survive a re-import otherwise; and `$EXTMAX` is right for a model below zero (OpenSCAD seeds its
1144
+ maxima with `numeric_limits<double>::min()`, the smallest POSITIVE double). Every number goes
1145
+ through the classic locale, the file stream included: Qt sets the process locale, and a grouping
1146
+ locale writes a 1500-vertex count as `1.500`.
1123
1147
  **`writePdf` is the other 2D writer**, and shares `collect2d()` with `writeSvg` so the all-2D
1124
1148
  rule and its refusal have one implementation. Everything else differs from SVG: the drawing is
1125
1149
  **centred on a fixed paper size** rather than the page being cut to fit, and **PDF user space is
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.31.0
3
+ Version: 1.33.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -436,14 +436,30 @@ nb::list exportModelPy(const std::string& path, const Geometry& geom, const std:
436
436
  bool stripSlivers, bool splitComponents, bool splitColors, bool svgFill,
437
437
  const std::string& svgFillColor,
438
438
  bool svgStroke, const std::string& svgStrokeColor, double svgStrokeWidth,
439
- nb::object pdfOptions) {
439
+ nb::object pdfOptions, nb::object povCamera) {
440
440
  oscadeval::ExportPdfOptions pdf;
441
441
  if (!pdfOptions.is_none()) applyPdfOptions(nb::cast<nb::dict>(pdfOptions), pdf);
442
+ oscadeval::ExportPovOptions pov;
443
+ if (!povCamera.is_none()) {
444
+ const std::vector<double> c = nb::cast<std::vector<double>>(povCamera);
445
+ if (c.size() != 8) {
446
+ throw std::runtime_error("pov_camera takes 8 numbers: $vpt x, y, z, $vpr x, y, z, distance, fov");
447
+ }
448
+ oscadeval::ExportPovCamera cam;
449
+ for (int k = 0; k < 3; ++k) {
450
+ cam.vpt[k] = c[k];
451
+ cam.vpr[k] = c[3 + k];
452
+ }
453
+ cam.distance = c[6];
454
+ cam.fov = c[7];
455
+ pov.camera = cam;
456
+ }
442
457
  std::vector<std::string> warnings;
443
458
  {
444
459
  nb::gil_scoped_release rel;
445
460
  oscadeval::ExportOptions opts;
446
461
  opts.pdf = pdf;
462
+ opts.pov = pov;
447
463
  opts.format = format;
448
464
  opts.asciiStl = asciiStl;
449
465
  opts.stripSlivers = stripSlivers;
@@ -957,7 +973,7 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
957
973
  nb::arg("svg_fill") = false,
958
974
  nb::arg("svg_fill_color") = std::string("white"), nb::arg("svg_stroke") = true,
959
975
  nb::arg("svg_stroke_color") = std::string("black"), nb::arg("svg_stroke_width") = 0.35,
960
- nb::arg("pdf_options") = nb::none(),
976
+ nb::arg("pdf_options") = nb::none(), nb::arg("pov_camera") = nb::none(),
961
977
  "Write `geometry` to `path`, format taken from the extension unless `format` says otherwise. "
962
978
  "Returns the warnings to surface (open shells, mesh problems, slivers removed) rather than "
963
979
  "logging them. Raises RuntimeError when there is no geometry, the format is unknown, or the "
@@ -976,7 +992,10 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
976
992
  "(paper-size, orientation, show-scale, show-scale-message, show-grid, grid-size, "
977
993
  "show-filename, design-filename, fill, fill-color, stroke, stroke-color, stroke-width, "
978
994
  "add-meta-data, title, author, subject, keywords). An unknown key raises rather than "
979
- "being ignored. .pdf is 2D-only too, and centres the drawing on a fixed paper size.");
995
+ "being ignored. .pdf is 2D-only too, and centres the drawing on a fixed paper size.\n\n"
996
+ "pov_camera is .pov's camera as 8 numbers -- $vpt x, y, z, $vpr x, y, z, distance, fov -- "
997
+ "so a POV-Ray render frames what the viewport showed. None frames the model from its "
998
+ "bounding box, as OpenSCAD does without a camera.");
980
999
 
981
1000
  m.def("list_fonts", []() {
982
1001
  nb::list out;
@@ -4,6 +4,7 @@
4
4
 
5
5
  #include <array>
6
6
  #include <cstdint>
7
+ #include <optional>
7
8
  #include <string>
8
9
  #include <vector>
9
10
 
@@ -158,6 +159,17 @@ struct ExportSvgOptions {
158
159
  void writeSvg(const std::string& path, const std::vector<ColoredBody>& bodies,
159
160
  const ExportSvgOptions& opts = {});
160
161
 
162
+ // DXF, the third 2D writer: OpenSCAD's own export_dxf.cc layout (an AC1006
163
+ // header with the model's extents, one CONTINUOUS linetype, layer 0) and
164
+ // every contour of every body a closed LWPOLYLINE on layer 0 -- holes are
165
+ // contours like any other, as in OpenSCAD. A 1-vertex contour is a POINT
166
+ // and a 2-vertex one a LINE, again as OpenSCAD writes them. Shares
167
+ // collect2d() with writeSvg/writePdf, so a 3D model is refused the same
168
+ // way. Coordinates are written at full round-trip precision, NOT OpenSCAD's
169
+ // 6 significant digits: a DXF goes to a cutter, where 1234.567 written as
170
+ // 1234.57 is a real error.
171
+ void writeDxf(const std::string& path, const std::vector<ColoredBody>& bodies);
172
+
161
173
  // Options for the PDF writer, named and defaulted after OpenSCAD's own
162
174
  // `-O export-pdf/...` set.
163
175
  struct ExportPdfOptions {
@@ -222,6 +234,33 @@ std::vector<std::string> writePdf(const std::string& path, const std::vector<Col
222
234
 
223
235
  // -- one entry point ------------------------------------------------------
224
236
 
237
+ // POV-Ray's camera, as OpenSCAD's viewport states it: $vpt, $vpr, $vpd and
238
+ // the field of view. Without one the scene is framed from the model's
239
+ // bounding box, as OpenSCAD frames it when it has no camera to pass.
240
+ struct ExportPovCamera {
241
+ double vpt[3] = {0, 0, 0};
242
+ double vpr[3] = {0, 0, 0};
243
+ double distance = 0;
244
+ double fov = 22.5;
245
+ };
246
+
247
+ struct ExportPovOptions {
248
+ std::optional<ExportPovCamera> camera;
249
+ };
250
+
251
+ // POV-Ray scene description. Every object splitBodiesForExport produces is
252
+ // one mesh2 -- a welded vertex list, a texture per colour, and a texture
253
+ // index per triangle, so per-triangle colour needs no unwelding -- with
254
+ // inside_vector, which makes it a SOLID to POV-Ray (refraction, CSG). That
255
+ // is the difference from OpenSCAD's export_pov.cc, which writes every face
256
+ // as its own flat polygon object. The rest of the scene is OpenSCAD's: its
257
+ // header, MATERIAL finish and ior 1.32 interior, 27 lights around the
258
+ // bounding box, its camera, and radiosity from rad_def.inc. Colour is rgbf
259
+ // with filter = 1 - alpha, as OpenSCAD writes it. Numbers are the shortest
260
+ // text that round-trips, in the classic locale.
261
+ void writePov(const std::string& path, const std::vector<ExportObject>& objects,
262
+ const ExportPovOptions& opts = {});
263
+
225
264
  struct ExportOptions {
226
265
  // Empty means "decide from the path's extension".
227
266
  std::string format;
@@ -247,6 +286,8 @@ struct ExportOptions {
247
286
  ExportSvgOptions svg;
248
287
  // .pdf only.
249
288
  ExportPdfOptions pdf;
289
+ // .pov only.
290
+ ExportPovOptions pov;
250
291
  };
251
292
 
252
293
  // 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.31.0"
7
+ version = "1.33.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -331,7 +331,8 @@ def export_model(path: str, geometry, format: str = "", ascii_stl: bool = False,
331
331
  split_colors: bool = True,
332
332
  svg_fill: bool = False, svg_fill_color: str = "white",
333
333
  svg_stroke: bool = True, svg_stroke_color: str = "black",
334
- svg_stroke_width: float = 0.35, pdf_options: dict | None = None) -> list:
334
+ svg_stroke_width: float = 0.35, pdf_options: dict | None = None,
335
+ pov_camera=None) -> list:
335
336
  """Write `geometry` to `path`. Returns the warnings to surface.
336
337
 
337
338
  `geometry` is the handle an Evaluator stashes on itself as
@@ -374,12 +375,17 @@ def export_model(path: str, geometry, format: str = "", ascii_stl: bool = False,
374
375
  size with nothing to explain it. `.pdf` is 2D-only like `.svg`, and
375
376
  centres the drawing on a fixed paper size instead of cutting the page
376
377
  to fit.
378
+
379
+ `pov_camera` is `.pov`'s camera as eight numbers -- `$vpt` x, y, z,
380
+ `$vpr` x, y, z, distance, field of view -- so a POV-Ray render frames
381
+ what the viewport showed. None frames the model from its bounding box,
382
+ as OpenSCAD does when it has no camera to pass.
377
383
  """
378
384
  try:
379
385
  return list(_ext.export_model(path, geometry, format, ascii_stl, strip_slivers,
380
386
  split_components, split_colors, svg_fill, svg_fill_color,
381
387
  svg_stroke, svg_stroke_color, svg_stroke_width,
382
- pdf_options))
388
+ pdf_options, pov_camera))
383
389
  except Exception as e:
384
390
  raise EvalError(str(e)) from e
385
391
 
@@ -13,6 +13,8 @@
13
13
  #include <cstdio>
14
14
  #include <ctime>
15
15
  #include <fstream>
16
+ #include <locale>
17
+ #include <sstream>
16
18
  #include <optional>
17
19
  #include <stdexcept>
18
20
 
@@ -913,7 +915,8 @@ std::string lowerExtension(const std::string& path) {
913
915
  }
914
916
 
915
917
  bool isMultiObject(const std::string& ext) {
916
- return ext == ".3mf" || ext == ".amf" || ext == ".obj" || ext == ".ply" || ext == ".wrl" || ext == ".x3d";
918
+ return ext == ".3mf" || ext == ".amf" || ext == ".obj" || ext == ".ply" || ext == ".pov" || ext == ".wrl" ||
919
+ ext == ".x3d";
917
920
  }
918
921
 
919
922
  // The merged single mesh STL/OFF write, with the open shells kept.
@@ -1155,6 +1158,258 @@ void writeSvg(const std::string& path, const std::vector<ColoredBody>& bodies, c
1155
1158
 
1156
1159
  namespace {
1157
1160
 
1161
+ // The shortest decimal that reads back as exactly `v`, in the classic
1162
+ // locale (Qt sets the process locale from the environment, so printf could
1163
+ // write "1,5" on a German system and no DXF reader would take it). -0 is
1164
+ // written as 0, and a non-finite value -- which no contour should hold --
1165
+ // as 0 rather than as text a reader would choke on.
1166
+ std::string dxfNum(double v) {
1167
+ if (!std::isfinite(v) || v == 0.0) return "0";
1168
+ for (int digits = 6; digits <= 17; ++digits) {
1169
+ std::ostringstream o;
1170
+ o.imbue(std::locale::classic());
1171
+ o.precision(digits);
1172
+ o << v;
1173
+ std::istringstream in(o.str());
1174
+ in.imbue(std::locale::classic());
1175
+ double back = 0.0;
1176
+ in >> back;
1177
+ if (back == v) return o.str();
1178
+ }
1179
+ std::ostringstream o; // unreachable: 17 digits always round-trips a double
1180
+ o.imbue(std::locale::classic());
1181
+ o.precision(17);
1182
+ o << v;
1183
+ return o.str();
1184
+ }
1185
+
1186
+ // The header, tables and (empty) blocks sections, verbatim from OpenSCAD's
1187
+ // export_dxf.cc -- its comments record which readers each piece was needed
1188
+ // for (Inkscape, LibreCAD, Illustrator, sharecad.org, generic cutters).
1189
+ void writeDxfHeader(std::ostream& out, double xMin, double yMin, double xMax, double yMax) {
1190
+ out << "999\nDXF from OpenSCAD\n"
1191
+ << " 0\nSECTION\n 2\nHEADER\n"
1192
+ << " 9\n$ACADVER\n 1\nAC1006\n"
1193
+ << " 9\n$INSBASE\n 10\n0.0\n 20\n0.0\n 30\n0.0\n";
1194
+ for (const char* name : {"$EXTMIN", "$EXTMAX", "$LINMIN", "$LINMAX"}) {
1195
+ const bool isMin = std::string(name).find("MIN") != std::string::npos;
1196
+ out << " 9\n" << name << "\n 10\n" << dxfNum(isMin ? xMin : xMax) << "\n 20\n"
1197
+ << dxfNum(isMin ? yMin : yMax) << "\n";
1198
+ }
1199
+ out << " 0\nENDSEC\n"
1200
+ << " 0\nSECTION\n 2\nTABLES\n"
1201
+ << " 0\nTABLE\n 2\nLTYPE\n 70\n1\n"
1202
+ << " 0\nLTYPE\n 2\nCONTINUOUS\n 70\n64\n 3\nSolid line\n 72\n65\n 73\n0\n 40\n0.000000\n"
1203
+ << " 0\nENDTAB\n"
1204
+ << " 0\nTABLE\n 2\nLAYER\n 70\n6\n"
1205
+ << " 0\nLAYER\n 2\n0\n 70\n64\n 62\n7\n 6\nCONTINUOUS\n"
1206
+ << " 0\nENDTAB\n"
1207
+ << " 0\nTABLE\n 2\nSTYLE\n 70\n0\n 0\nENDTAB\n"
1208
+ << " 0\nENDSEC\n"
1209
+ << " 0\nSECTION\n 2\nBLOCKS\n 0\nENDSEC\n";
1210
+ }
1211
+
1212
+ } // namespace
1213
+
1214
+ void writeDxf(const std::string& path, const std::vector<ColoredBody>& bodies) {
1215
+ const Flat2d flat = collect2d(bodies);
1216
+
1217
+ std::ofstream out(path);
1218
+ if (!out) throw std::runtime_error("Could not open '" + path + "' for writing");
1219
+ // The vertex counts go through the stream itself, which otherwise takes
1220
+ // the global C++ locale -- one with digit grouping writes 1500 as "1.500".
1221
+ out.imbue(std::locale::classic());
1222
+
1223
+ // Extents from collect2d's own bounding box. OpenSCAD starts its maxima
1224
+ // at numeric_limits<double>::min() -- the smallest POSITIVE double, not
1225
+ // the most negative -- so a model lying entirely at negative x or y gets
1226
+ // a wrong $EXTMAX there. That is a bug, not a format rule.
1227
+ writeDxfHeader(out, flat.minx, flat.miny, flat.maxx, flat.maxy);
1228
+
1229
+ out << " 0\nSECTION\n 2\nENTITIES\n";
1230
+ for (const manifold::Polygons& polys : flat.perBody) {
1231
+ for (const manifold::SimplePolygon& contour : polys) {
1232
+ const auto xy = [&](const char* xCode, const char* yCode, const manifold::vec2& p) {
1233
+ out << xCode << "\n" << dxfNum(p.x) << "\n" << yCode << "\n" << dxfNum(p.y) << "\n";
1234
+ };
1235
+ switch (contour.size()) {
1236
+ case 0:
1237
+ break;
1238
+ case 1:
1239
+ out << " 0\nPOINT\n100\nAcDbEntity\n 8\n0\n100\nAcDbPoint\n";
1240
+ xy(" 10", " 20", contour[0]);
1241
+ break;
1242
+ case 2:
1243
+ // [X1 Y1 X2 Y2]: the order ezdxf and dxfgrabber can read.
1244
+ out << " 0\nLINE\n100\nAcDbEntity\n 8\n0\n100\nAcDbLine\n";
1245
+ xy(" 10", " 20", contour[0]);
1246
+ xy(" 11", " 21", contour[1]);
1247
+ break;
1248
+ default:
1249
+ out << " 0\nLWPOLYLINE\n100\nAcDbEntity\n 8\n0\n100\nAcDbPolyline\n"
1250
+ << " 90\n" << contour.size() << "\n 70\n1\n"; // 70 = 1: closed
1251
+ for (const manifold::vec2& p : contour) xy(" 10", " 20", p);
1252
+ }
1253
+ }
1254
+ }
1255
+ out << " 0\nENDSEC\n 0\nEOF\n";
1256
+ }
1257
+
1258
+ namespace {
1259
+
1260
+ // dxfNum's rule for a float: the shortest text that reads back as the same
1261
+ // float. Mesh vertices and colours are floats, and printing them as doubles
1262
+ // would turn 0.1f into 0.100000001490116.
1263
+ std::string floatNum(float v) {
1264
+ if (!std::isfinite(v) || v == 0.0f) return "0";
1265
+ for (int digits = 6; digits <= 9; ++digits) {
1266
+ std::ostringstream o;
1267
+ o.imbue(std::locale::classic());
1268
+ o.precision(digits);
1269
+ o << v;
1270
+ std::istringstream in(o.str());
1271
+ in.imbue(std::locale::classic());
1272
+ float back = 0.0f;
1273
+ in >> back;
1274
+ if (back == v) return o.str();
1275
+ }
1276
+ std::ostringstream o; // unreachable: 9 digits always round-trips a float
1277
+ o.imbue(std::locale::classic());
1278
+ o.precision(9);
1279
+ o << v;
1280
+ return o.str();
1281
+ }
1282
+
1283
+ std::string povVec(double x, double y, double z) {
1284
+ return "<" + dxfNum(x) + ", " + dxfNum(y) + ", " + dxfNum(z) + ">";
1285
+ }
1286
+
1287
+ } // namespace
1288
+
1289
+ void writePov(const std::string& path, const std::vector<ExportObject>& objects, const ExportPovOptions& opts) {
1290
+ std::ofstream out(path);
1291
+ if (!out) throw std::runtime_error("Could not open '" + path + "' for writing");
1292
+ out.imbue(std::locale::classic());
1293
+
1294
+ out << "// Generated by BelfrySCAD (openscad_cpp_evaluator)\n\n"
1295
+ << "// Camera settings are at the bottom of this script\n\n"
1296
+ << "#version 3.7;\n"
1297
+ << "global_settings { assumed_gamma 1.0 }\n"
1298
+ << "#declare MATERIAL=finish { specular 0.5 roughness 0.001 reflection{0 0.63 fresnel} ambient 0 "
1299
+ "diffuse 0.6 conserve_energy }\n"
1300
+ << "#declare MATERIAL_INT=interior{ior 1.32}\n";
1301
+
1302
+ const auto rgbf = [](const std::array<float, 4>& c) {
1303
+ return "<" + floatNum(c[0]) + ", " + floatNum(c[1]) + ", " + floatNum(c[2]) + ", " +
1304
+ floatNum(1.0f - c[3]) + ">";
1305
+ };
1306
+
1307
+ bool any = false;
1308
+ double lo[3] = {0, 0, 0}, hi[3] = {0, 0, 0};
1309
+ for (const ExportObject& o : objects) {
1310
+ const size_t vertCount = o.verts.size() / 3;
1311
+ const size_t triCount = o.tris.size() / 3;
1312
+ if (vertCount == 0 || triCount == 0) continue;
1313
+ for (size_t v = 0; v < vertCount; ++v) {
1314
+ for (int k = 0; k < 3; ++k) {
1315
+ const double c = o.verts[v * 3 + k];
1316
+ if (!any) {
1317
+ lo[k] = hi[k] = c;
1318
+ } else {
1319
+ lo[k] = std::min(lo[k], c);
1320
+ hi[k] = std::max(hi[k], c);
1321
+ }
1322
+ }
1323
+ any = true;
1324
+ }
1325
+
1326
+ // One texture per distinct colour, in order of first use.
1327
+ std::vector<std::array<float, 4>> palette;
1328
+ std::vector<size_t> triTexture(triCount, 0);
1329
+ const auto textureOf = [&](const std::array<float, 4>& c) {
1330
+ for (size_t i = 0; i < palette.size(); ++i) {
1331
+ if (palette[i] == c) return i;
1332
+ }
1333
+ palette.push_back(c);
1334
+ return palette.size() - 1;
1335
+ };
1336
+ for (size_t t = 0; t < triCount; ++t) {
1337
+ triTexture[t] = textureOf(o.triColors.empty() ? o.color : o.triColors[t]);
1338
+ }
1339
+
1340
+ out << "mesh2 {\n vertex_vectors { " << vertCount;
1341
+ for (size_t v = 0; v < vertCount; ++v) {
1342
+ out << ",\n <" << floatNum(o.verts[v * 3]) << ", " << floatNum(o.verts[v * 3 + 1]) << ", "
1343
+ << floatNum(o.verts[v * 3 + 2]) << ">";
1344
+ }
1345
+ out << "\n }\n texture_list { " << palette.size();
1346
+ for (const std::array<float, 4>& c : palette) {
1347
+ out << "\n texture { pigment { color rgbf " << rgbf(c) << " } finish { MATERIAL } }";
1348
+ }
1349
+ out << "\n }\n face_indices { " << triCount;
1350
+ for (size_t t = 0; t < triCount; ++t) {
1351
+ out << ",\n <" << o.tris[t * 3] << ", " << o.tris[t * 3 + 1] << ", " << o.tris[t * 3 + 2] << ">, "
1352
+ << triTexture[t];
1353
+ }
1354
+ // inside_vector: without it POV-Ray treats a mesh as a bare surface,
1355
+ // with no inside for the interior (and its ior) or CSG to act on.
1356
+ out << "\n }\n inside_vector <0, 0, 1>\n interior { MATERIAL_INT }\n}\n";
1357
+ }
1358
+ if (!any) throw std::runtime_error("No geometry to export");
1359
+
1360
+ // 27 lights: every combination of {below, centre, above} on each axis,
1361
+ // twice the model's size out, at OpenSCAD's brightness.
1362
+ const double move = 2.0;
1363
+ double at[3][3];
1364
+ for (int k = 0; k < 3; ++k) {
1365
+ const double d = hi[k] - lo[k];
1366
+ at[k][0] = lo[k] - d * move;
1367
+ at[k][1] = (lo[k] + hi[k]) / 2.0;
1368
+ at[k][2] = hi[k] + d * move;
1369
+ }
1370
+ for (double x : at[0]) {
1371
+ for (double y : at[1]) {
1372
+ for (double z : at[2]) {
1373
+ out << "light_source { " << povVec(x, y, z) << " color rgb <0.2, 0.2, 0.2> }\n";
1374
+ }
1375
+ }
1376
+ }
1377
+
1378
+ if (opts.camera) {
1379
+ // OpenSCAD's camera block, with two fixes, both checked by rendering
1380
+ // in POV-Ray against BelfrySCAD's own image of the same camera:
1381
+ // - rotate BEFORE translate. The eye orbits $vpt, so it is rotated
1382
+ // about the origin and then moved; OpenSCAD translates first,
1383
+ // which rotates $vpt itself away and frames the wrong place
1384
+ // whenever a view both pans and turns.
1385
+ // - the field of view is VERTICAL in the viewport and HORIZONTAL to
1386
+ // POV-Ray's `angle`. OpenSCAD writes it straight across, so its
1387
+ // renders come out zoomed in by the aspect ratio (1.33x at 4:3).
1388
+ // The horizontal angle is left to POV-Ray to work out from the
1389
+ // image it is asked for, so any size frames the same.
1390
+ const ExportPovCamera& c = *opts.camera;
1391
+ out << "camera { look_at <0, 0, 0>\n"
1392
+ << " location <0, 0, " << dxfNum(c.distance) << ">\n"
1393
+ << " angle degrees(2 * atan(tan(radians(" << dxfNum(c.fov) << " / 2)) * image_width / image_height))"
1394
+ << " up <0, 1, 0> right <1, 0, 0> sky <0, 1, 0> right -x*image_width/image_height\n"
1395
+ << "rotate <" << dxfNum(c.vpr[0]) << ", " << dxfNum(c.vpr[1]) << " + clock * 3, " << dxfNum(c.vpr[2])
1396
+ << " + clock>\n"
1397
+ << "translate " << povVec(c.vpt[0], c.vpt[1], c.vpt[2]) << "\n"
1398
+ << "}\n";
1399
+ } else {
1400
+ const double dx = hi[0] - lo[0], dy = hi[1] - lo[1], dz = hi[2] - lo[2];
1401
+ out << "camera { look_at " << povVec((lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2)
1402
+ << " location " << povVec(lo[0] + dx * move, lo[1] - dy * move, lo[2] + dz * move)
1403
+ << " up <0, 0, 1> right <1, 0, 0> sky <0, 0, 1> rotate <-55, clock * 3, clock + 25> right "
1404
+ "x*image_width/image_height }\n";
1405
+ }
1406
+ out << "#include \"rad_def.inc\"\n"
1407
+ << "global_settings { photons { count 20000 autostop 0 jitter .4 } radiosity { "
1408
+ "Rad_Settings(Radiosity_Normal, off, off) } }\n";
1409
+ }
1410
+
1411
+ namespace {
1412
+
1158
1413
  // PDF's own unit is 1/72 inch; models are millimetres.
1159
1414
  constexpr double kPtPerMm = 72.0 / 25.4;
1160
1415
  // Page margin the ruler is drawn on, in points. OpenSCAD's own MARGIN.
@@ -1497,8 +1752,8 @@ std::vector<std::string> writePdf(const std::string& path, const std::vector<Col
1497
1752
  }
1498
1753
 
1499
1754
  const std::vector<std::string>& exportExtensions() {
1500
- static const std::vector<std::string> exts = {".3mf", ".amf", ".pdf", ".stl", ".obj",
1501
- ".off", ".ply", ".svg", ".wrl", ".x3d"};
1755
+ static const std::vector<std::string> exts = {".3mf", ".amf", ".dxf", ".pdf", ".stl", ".obj",
1756
+ ".off", ".ply", ".pov", ".svg", ".wrl", ".x3d"};
1502
1757
  return exts;
1503
1758
  }
1504
1759
 
@@ -1524,6 +1779,12 @@ std::vector<std::string> exportModel(const std::string& path, const std::vector<
1524
1779
  return writePdf(path, bodies, opts.pdf);
1525
1780
  }
1526
1781
 
1782
+ if (ext == ".dxf") {
1783
+ // 2D, outside the mesh pipeline like SVG and PDF.
1784
+ writeDxf(path, bodies);
1785
+ return warnings;
1786
+ }
1787
+
1527
1788
  if (ext == ".svg") {
1528
1789
  // 2D, and nothing the mesh pipeline below does applies: no merge,
1529
1790
  // no sliver strip, no manifoldness check. Contours are what they
@@ -1549,6 +1810,8 @@ std::vector<std::string> exportModel(const std::string& path, const std::vector<
1549
1810
  writeObj(path, objects);
1550
1811
  } else if (ext == ".ply") {
1551
1812
  writePly(path, objects);
1813
+ } else if (ext == ".pov") {
1814
+ writePov(path, objects, opts.pov);
1552
1815
  } else if (ext == ".wrl") {
1553
1816
  writeVrml(path, objects);
1554
1817
  } else {