openscad-cpp-evaluator 1.26.0__tar.gz → 1.28.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/CLAUDE.md +19 -7
  2. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/bindings/module.cpp +16 -6
  4. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/colored_body.hpp +8 -1
  5. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +6 -1
  6. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/pyproject.toml +1 -1
  7. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/python/openscad_cpp_evaluator/__init__.py +11 -3
  8. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/dxf_dim.cpp +29 -6
  9. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/import.cpp +37 -7
  10. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/colored_body.cpp +4 -6
  11. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/coverage.cpp +15 -0
  12. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/export.cpp +5 -1
  13. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/import/svg_import.cpp +103 -2
  14. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_coverage.cpp +36 -0
  15. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_dxf_svg_import.cpp +98 -1
  16. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_export.cpp +47 -0
  17. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_import_export.cpp +33 -2
  18. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_python_bindings.py +25 -0
  19. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/uv.lock +1 -1
  20. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/.github/workflows/ci.yml +0 -0
  21. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/.github/workflows/wheels.yml +0 -0
  22. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/.gitignore +0 -0
  23. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/.gitmodules +0 -0
  24. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/LICENSE +0 -0
  26. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/README.md +0 -0
  27. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/bindings/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/bindings/ast_to_py.cpp +0 -0
  29. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/cmake/embed_font.cmake +0 -0
  30. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/examples/CMakeLists.txt +0 -0
  31. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/examples/eval_perf_benchmark.cpp +0 -0
  32. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/examples/manifold_cache_reuse.cpp +0 -0
  33. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/examples/minimal_debugger.cpp +0 -0
  34. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  35. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/.gitignore +0 -0
  36. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  37. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  38. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/README.md +0 -0
  39. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  40. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  41. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  43. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  44. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  45. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  46. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  47. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  48. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  49. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  50. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  51. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  52. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  53. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  54. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  55. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  56. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  57. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  58. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  59. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  60. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  61. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  62. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  63. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  64. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  65. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  66. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  67. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  68. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  69. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  70. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  71. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  72. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  73. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  74. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  75. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  76. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  77. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  78. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  79. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  80. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  81. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  82. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  83. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  84. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  85. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  86. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  87. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  88. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  89. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  90. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  91. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  92. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  93. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  94. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  95. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  96. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  97. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  98. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  99. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  100. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  101. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  102. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  103. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  104. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  105. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  106. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  107. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  108. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  109. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  110. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  111. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  112. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  113. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  114. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  115. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  116. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  117. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  118. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  119. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  120. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  121. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  122. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  123. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  124. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  125. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  126. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  127. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  128. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  129. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  130. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  131. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  132. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  133. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  134. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  135. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  136. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  137. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/resources/fonts/LICENSE +0 -0
  138. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  139. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  140. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  141. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  142. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/CMakeLists.txt +0 -0
  143. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/booleans.cpp +0 -0
  144. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/builtins.hpp +0 -0
  145. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/call_args.cpp +0 -0
  146. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/color.cpp +0 -0
  147. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/control.cpp +0 -0
  148. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/extrude.cpp +0 -0
  149. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/function_builtins.cpp +0 -0
  150. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/modifiers.cpp +0 -0
  151. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/primitives_2d.cpp +0 -0
  152. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/primitives_3d.cpp +0 -0
  153. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/registry.cpp +0 -0
  154. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/roof.cpp +0 -0
  155. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/segments.cpp +0 -0
  156. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/surface.cpp +0 -0
  157. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/text.cpp +0 -0
  158. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/topology.cpp +0 -0
  159. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/builtins/transforms.cpp +0 -0
  160. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/bytecode_compiler.cpp +0 -0
  161. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/bytecode_vm.cpp +0 -0
  162. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/csg_generate.cpp +0 -0
  163. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/csg_resolve.cpp +0 -0
  164. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/css_colors.cpp +0 -0
  165. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/debug/debug_repl.cpp +0 -0
  166. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/debug_profile.cpp +0 -0
  167. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/eval_context.cpp +0 -0
  168. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/eval_error.cpp +0 -0
  169. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/eval_use.cpp +0 -0
  170. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/expr_eval.cpp +0 -0
  171. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/format_closure.cpp +0 -0
  172. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/import/dxf_import.cpp +0 -0
  173. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/import/mesh_import.cpp +0 -0
  174. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/import/surface_load.cpp +0 -0
  175. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/manifold_cache.cpp +0 -0
  176. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/measure_geometry.cpp +0 -0
  177. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/mesh_check.cpp +0 -0
  178. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/stmt_eval.cpp +0 -0
  179. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/text/font_match.cpp +0 -0
  180. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/text/freetype_font_provider.cpp +0 -0
  181. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/text/text_metrics.cpp +0 -0
  182. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/user_calls.cpp +0 -0
  183. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/utf8.cpp +0 -0
  184. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/value.cpp +0 -0
  185. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/src/zip_stored.cpp +0 -0
  186. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/CMakeLists.txt +0 -0
  187. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_booleans.cpp +0 -0
  188. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_bytecode_compiler.cpp +0 -0
  189. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_children_separate.cpp +0 -0
  190. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_cli.cpp +0 -0
  191. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_control_flow.cpp +0 -0
  192. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_csg_tree.cpp +0 -0
  193. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_debug_hooks.cpp +0 -0
  194. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_eval_context.cpp +0 -0
  195. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_expr_eval.cpp +0 -0
  196. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_extrude_roof.cpp +0 -0
  197. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_feature_detection.cpp +0 -0
  198. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_function_builtins.cpp +0 -0
  199. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_helpers.hpp +0 -0
  200. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_include_cache.cpp +0 -0
  201. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_manifold_cache.cpp +0 -0
  202. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_mesh_check.cpp +0 -0
  203. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_multi_color_merge.cpp +0 -0
  204. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_primitives.cpp +0 -0
  205. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_profiling.cpp +0 -0
  206. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_render_expr.cpp +0 -0
  207. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_scope_trail.cpp +0 -0
  208. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_scoping.cpp +0 -0
  209. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_surface.cpp +0 -0
  210. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_tail_calls.cpp +0 -0
  211. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_text.cpp +0 -0
  212. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_transforms.cpp +0 -0
  213. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_unexpected_args.cpp +0 -0
  214. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_use.cpp +0 -0
  215. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_value.cpp +0 -0
  216. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_viewport_params.cpp +0 -0
  217. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tests/test_zip_stored.cpp +0 -0
  218. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tools/cli/CMakeLists.txt +0 -0
  219. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tools/cli/cli_lib.cpp +0 -0
  220. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tools/cli/cli_lib.hpp +0 -0
  221. {openscad_cpp_evaluator-1.26.0 → openscad_cpp_evaluator-1.28.0}/tools/cli/main.cpp +0 -0
@@ -984,15 +984,16 @@ grep for `ponytail:`.
984
984
  rather than out of the model. Ported from the reference's `io/dxfdim.cc` plus the DIMENSION/LINE
985
985
  half of `io/DxfData.cc`: `dxf_dim()` returns the first matching DIMENSION's value by its type
986
986
  (`70 & 7` — 0 rotated, 1 aligned, 2 angular, 3/4 diameter/radius, 6 ordinate; type 5 is
987
- unsupported there too), `dxf_cross()` intersects the first two LINE entities. Note the reference's
987
+ unsupported there too), `dxf_cross()` intersects the first two 2-point paths. Note the reference's
988
988
  own coordinate quirk, reproduced here: groups 11/12/16 (and 21/22/26) are scaled but NOT
989
989
  origin-shifted, being extents rather than positions.
990
990
 
991
991
  Deliberately its own group-code walk rather than sharing `src/import/dxf_import.cpp`'s: that one
992
992
  produces closed contours, and these need the DIMENSION entities' seven coordinate slots and the
993
- raw LINE endpoints, neither of which a contour list keeps. One divergence: `dxf_cross()` takes the
994
- first two LINEs, where the reference walks every 2-point path — for a real cross (two strokes
995
- meeting in the middle, not at their endpoints) that is the same set.
993
+ raw LINE endpoints, neither of which a contour list keeps. A 2-point path is a LINE sharing
994
+ neither end with another LINE or an ARC (an ARC's ends read from its centre, radius and 50/51
995
+ angles): one that does belongs to an outline. Taking the first two LINEs outright found a "cross"
996
+ in example009.dxf's `fan_top` outline where the reference warns `Can't find cross`.
996
997
 
997
998
  - `src/csg_generate.cpp` — `Evaluator::applyDimensionRules`, run centrally just before every node's
998
999
  `GenerateFn` (and once more over the top-level list, which is an implicit union). Mirrors the
@@ -1135,6 +1136,12 @@ grep for `ponytail:`.
1135
1136
  `writeSvg`, which needs the sections that step throws away, and which says so rather than writing
1136
1137
  the slab's outline. `tools/cli/cli_lib.cpp` is the only current caller; a future GUI/renderer
1137
1138
  caller needs the same call before passing results to its own mesh consumer.
1139
+ **The slab height is a parameter** (`flatHeight`, default `kTopLevel2dHeight` = 1.0, the
1140
+ reference's measured preview height that docs images and `--viewall` framing depend on). Python
1141
+ reaches it as `Evaluator(flat_preview_height=...)`, which thins only the DISPLAYED bodies:
1142
+ `evaluate()` converts the raw list a second time at 1.0 for `geometry`, because a 2D-only script
1143
+ exports its slab and that must not become whatever a viewer found easiest to look at. BelfrySCAD's
1144
+ viewport asks for 0.01 so a 2D shape reads as the flat contour it is.
1138
1145
  - `include/openscad_cpp_evaluator/function_builtins.hpp`, `src/builtins/function_builtins.cpp` —
1139
1146
  the math/string/list/type-check builtin *function* dispatch (`isBuiltinFunctionName`/
1140
1147
  `evalBuiltinFunction`), a completely separate namespace/table from `dispatch.hpp`'s builtin
@@ -1204,9 +1211,14 @@ grep for `ponytail:`.
1204
1211
  path-command tokenizer/flattener (`M`/`L`/`H`/`V`/`C`/`S`/`Q`/`T`/`A`, both absolute and relative,
1205
1212
  cubic/quadratic Bezier flattening, full elliptical-arc endpoint-to-center parameterization) plus
1206
1213
  `rect`/`circle`/`ellipse`/`polygon`/`polyline` and nested `transform="matrix()/translate()/
1207
- scale()/rotate()"` composition (including through `<g>` groups) — Y is flipped (SVG down →
1208
- OpenSCAD up) at the point-transform step, `applyMat()` (named to avoid colliding with
1209
- `std::apply` in an ADL lookup that broke the build once — see its own comment).
1214
+ scale()/rotate()"` composition (including through `<g>` groups), in SVG user units at the
1215
+ point-transform step, `applyMat()` (named to avoid colliding with `std::apply` in an ADL lookup
1216
+ that broke the build once — see its own comment). **Placement is `pageMap()`, a port of the
1217
+ reference's `import_svg.cc`**: the page's `width`/`height` to mm (a unitless length at `dpi=`,
1218
+ 72 by default; `px` at 96), the `viewBox` scaled onto the page under `preserveAspectRatio`
1219
+ (default `xMidYMid meet`), and Y flipped about the page HEIGHT -- or about the drawing's centre
1220
+ with `center=true`. Negating Y alone, as this did before, put a unitless 100-unit drawing in 2.8x
1221
+ too large and below the X axis. Bounds match OpenSCAD 2026.02.01 (`SvgPlacement` tests).
1210
1222
  - `include/openscad_cpp_evaluator/font_provider.hpp` — the `FontProvider` abstract interface
1211
1223
  (`resolveFont`/`metrics`/`shapeText`/`glyphInkBounds`/`glyphOutline`), the seam that keeps
1212
1224
  `text()`/`textmetrics()`/`fontmetrics()` decoupled from any specific font backend. `Evaluator`'s
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.26.0
3
+ Version: 1.28.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -505,10 +505,15 @@ nb::object coverageResultToPy(const std::optional<oscadeval::CoverageResult>& cr
505
505
 
506
506
  nb::object evaluate(const std::string& path, nb::dict viewportParams,
507
507
  std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, bool profile,
508
- bool generate, bool strictCommas, bool coverage, bool keepMinuendColor) {
508
+ bool generate, bool strictCommas, bool coverage, bool keepMinuendColor,
509
+ double flatPreviewHeight) {
509
510
  std::unordered_map<std::string, oscadeval::Value> vp = toViewportParams(viewportParams);
510
511
 
511
512
  std::vector<oscadeval::ColoredBody> bodies;
513
+ // What export gets. The same list as `bodies` unless the caller asked
514
+ // for a thinner preview slab: a 2D-only script exports its slab, and
515
+ // that must stay the reference's 1 unit whatever the viewer shows.
516
+ std::vector<oscadeval::ColoredBody> exportBodies;
512
517
  std::vector<std::string> echoes;
513
518
  std::vector<IdSpan> idSpans;
514
519
  std::vector<std::unique_ptr<oscadeval::CSGNode>> csgTree;
@@ -532,7 +537,10 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
532
537
  ev.keepMinuendColor = keepMinuendColor;
533
538
  ev.setUsedFileGlobals(used.usedFileGlobals);
534
539
  oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
535
- bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp, generate));
540
+ std::vector<oscadeval::ColoredBody> raw = ev.evaluate(used.processedNodes, ctx, vp, generate);
541
+ bodies = oscadeval::toRenderableBodies(raw, flatPreviewHeight);
542
+ exportBodies = flatPreviewHeight == oscadeval::kTopLevel2dHeight
543
+ ? bodies : oscadeval::toRenderableBodies(raw);
536
544
  collectIdSpans(ev, idSpans);
537
545
  // Only moved out when it will actually be converted below --
538
546
  // csgTreeToPy() walks the whole tree building Python objects,
@@ -567,8 +575,8 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
567
575
  nb::list echoList;
568
576
  for (const std::string& s : echoes) echoList.append(s);
569
577
  auto geom = std::make_shared<Geometry>();
570
- geom->bodies = std::move(bodies);
571
- return nb::make_tuple(bodiesToList(geom->bodies), echoList, idSpansToDict(idSpans), csgTreeToPy(csgTree),
578
+ geom->bodies = std::move(exportBodies);
579
+ return nb::make_tuple(bodiesToList(bodies), echoList, idSpansToDict(idSpans), csgTreeToPy(csgTree),
572
580
  profileResultToPy(profileResult), dyn, dynExplicit, geom, coverageResultToPy(coverageResult));
573
581
  }
574
582
 
@@ -780,7 +788,7 @@ void callPyReturnHook(nb::handle returnHook, const std::string& name, const osca
780
788
  nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::callable debugHook,
781
789
  nb::callable errorBreak, nb::callable echoFn,
782
790
  std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, nb::object returnHook,
783
- FastContinueSignal* fastContinueSignal) {
791
+ FastContinueSignal* fastContinueSignal, double flatPreviewHeight) {
784
792
  std::unordered_map<std::string, oscadeval::Value> vp = toViewportParams(viewportParams);
785
793
 
786
794
  std::vector<oscadeval::ColoredBody> bodies;
@@ -841,7 +849,7 @@ nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::c
841
849
  if (fastContinueSignal) ev.setFastContinueInterruptFlag(fastContinueSignal->flag());
842
850
  ev.setUsedFileGlobals(used.usedFileGlobals);
843
851
  oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
844
- bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp));
852
+ bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp), flatPreviewHeight);
845
853
  collectIdSpans(ev, idSpans);
846
854
  {
847
855
  nb::gil_scoped_acquire g;
@@ -985,6 +993,7 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
985
993
  m.def("evaluate", &evaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("manifold_cache") = nullptr,
986
994
  nb::arg("profile") = false, nb::arg("generate") = true, nb::arg("strict_commas") = false,
987
995
  nb::arg("coverage") = false, nb::arg("keep_minuend_color") = false,
996
+ nb::arg("flat_preview_height") = oscadeval::kTopLevel2dHeight,
988
997
  "Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit, "
989
998
  "geometry, coverage_result).\n"
990
999
  "coverage=True records which statements, branch arms and bodies ran: coverage_result is a dict "
@@ -1024,5 +1033,6 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
1024
1033
  m.def("debug_evaluate", &debugEvaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("debug_hook"),
1025
1034
  nb::arg("error_break"), nb::arg("echo_fn"), nb::arg("manifold_cache") = nullptr,
1026
1035
  nb::arg("return_hook") = nb::none(), nb::arg("fast_continue_signal") = nullptr,
1036
+ nb::arg("flat_preview_height") = oscadeval::kTopLevel2dHeight,
1027
1037
  "Evaluate with the debugger wired in; returns (bodies, [], id_to_node, dyn, dyn_explicit). Callbacks fire under the GIL.");
1028
1038
  }
@@ -169,6 +169,13 @@ std::optional<std::array<float, 4>> valueToColor(const Value& v);
169
169
  // (the CLI, any other mesh-export caller) should apply this to
170
170
  // Evaluator::evaluate()'s result before exporting, the same way the
171
171
  // reference's own cli.py does right before export_bodies().
172
- std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodies);
172
+ // The thickness toRenderableBodies() gives a top-level 2D shape's slab by
173
+ // default: OpenSCAD's own preview height, measured (see colored_body.cpp).
174
+ // Docs images and exports use it; an interactive viewer may pass a thinner
175
+ // one so a 2D shape reads as the flat contour it is.
176
+ inline constexpr double kTopLevel2dHeight = 1.0;
177
+
178
+ std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodies,
179
+ double flatHeight = kTopLevel2dHeight);
173
180
 
174
181
  } // namespace oscadeval
@@ -34,8 +34,13 @@ struct SvgFilter {
34
34
  // `matched`, when given, reports whether the filter found anything. A miss
35
35
  // imports nothing rather than falling back to the whole drawing; the caller
36
36
  // warns. Always true when no filter was set.
37
+ // Placed as OpenSCAD places them: page units to mm (a unitless length at
38
+ // `dpi`), the viewBox under preserveAspectRatio, Y flipped about the page
39
+ // height -- or about the drawing's centre with `center`.
37
40
  std::vector<Contour2d> loadSvgContours(const std::string& path,
38
41
  const SvgFilter& filter = {},
39
- bool* matched = nullptr);
42
+ bool* matched = nullptr,
43
+ double dpi = 72.0,
44
+ bool center = false);
40
45
 
41
46
  } // namespace oscadeval
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.26.0"
7
+ version = "1.28.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -565,7 +565,7 @@ class Evaluator:
565
565
 
566
566
  def __init__(self, echo_fn=None, debug_hook=None, error_break_fn=None, return_hook=None,
567
567
  manifold_cache=None, profile=False, fast_continue_signal=None, coverage=False,
568
- keep_minuend_color=False):
568
+ keep_minuend_color=False, flat_preview_height=1.0):
569
569
  self._echo_fn = echo_fn
570
570
  self._debug_hook = debug_hook
571
571
  self._error_break_fn = error_break_fn
@@ -579,6 +579,12 @@ class Evaluator:
579
579
  # subtrahend's (or the cut green). A viewer option; see
580
580
  # Evaluator::keepMinuendColor in evaluator.hpp.
581
581
  self._keep_minuend_color = keep_minuend_color
582
+ # How thick a top-level 2D shape's preview slab is in the returned
583
+ # bodies. 1.0 is OpenSCAD's preview height, which docs images and
584
+ # --viewall framing depend on; a viewer can pass something thin so
585
+ # a 2D shape reads as a flat contour. Export (`geometry`) always
586
+ # keeps 1.0: a 2D-only script exports its slab.
587
+ self._flat_preview_height = float(flat_preview_height)
582
588
  self.csg_tree = []
583
589
  self.profile_result = None
584
590
  # coverage=True: after evaluate(), {"spans": [...], "files": [...],
@@ -621,7 +627,8 @@ class Evaluator:
621
627
  source_path, vp, self._debug_hook,
622
628
  self._error_break_fn or (lambda *a, **k: None),
623
629
  self._echo_fn or (lambda _m: None),
624
- self._manifold_cache, self._return_hook, self._fast_continue_signal)
630
+ self._manifold_cache, self._return_hook, self._fast_continue_signal,
631
+ self._flat_preview_height)
625
632
  self.csg_tree = []
626
633
  self.profile_result = None
627
634
  # The debugger path has no geometry handle: debug_evaluate
@@ -632,7 +639,8 @@ class Evaluator:
632
639
  (body_dicts, echoes, id_spans, csg_tree, profile_result, dyn,
633
640
  dyn_explicit, geometry, coverage_result) = _ext.evaluate(
634
641
  source_path, vp, self._manifold_cache, self._profile, generate,
635
- strict_commas, self._coverage, self._keep_minuend_color)
642
+ strict_commas, self._coverage, self._keep_minuend_color,
643
+ self._flat_preview_height)
636
644
  # The evaluated bodies, still on the C++ side. Stashed like
637
645
  # csg_tree/profile_result rather than returned, so
638
646
  # evaluate()'s own 2-tuple result is unchanged -- callers
@@ -6,6 +6,7 @@
6
6
  #include <cmath>
7
7
  #include <filesystem>
8
8
  #include <fstream>
9
+ #include <map>
9
10
  #include <sstream>
10
11
 
11
12
  namespace oscadeval {
@@ -26,7 +27,9 @@ struct Entity {
26
27
  std::string layer;
27
28
  std::string name; // group 1: DIMENSION's text override
28
29
  int dimType = 0; // group 70
29
- double angle = 0.0; // group 50
30
+ double angle = 0.0; // group 50 (an ARC's start angle)
31
+ double angle2 = 0.0; // group 51: an ARC's end angle
32
+ double radius = 0.0; // group 40: an ARC's radius
30
33
  double coords[7][2] = {}; // groups 10-16 / 20-26
31
34
  std::vector<double> xverts, yverts; // groups 10/11 and 20/21, in order
32
35
  };
@@ -91,6 +94,8 @@ std::vector<Entity> readEntities(const std::string& path, double xorigin, double
91
94
  case 20:
92
95
  case 21: cur.yverts.push_back((num() - yorigin) * scale); break;
93
96
  case 50: cur.angle = num(); break;
97
+ case 51: cur.angle2 = num(); break;
98
+ case 40: cur.radius = num() * scale; break;
94
99
  case 70:
95
100
  try {
96
101
  cur.dimType = std::stoi(data);
@@ -188,16 +193,34 @@ Value builtinDxfCross(Evaluator& ev, const CallArgs& args, const oscad::ASTNode&
188
193
  return Value{};
189
194
  }
190
195
 
191
- // The first two LINEs on the layer are taken to be the cross. The
192
- // reference walks every 2-point path instead, which for a real cross is
193
- // the same set: its two strokes meet in the middle rather than at their
194
- // endpoints, so neither gets joined into a longer path.
196
+ // The first two 2-POINT PATHS on the layer are the cross, as the
197
+ // reference walks them: a LINE sharing an end with another LINE or an
198
+ // ARC is joined into a longer path, part of an outline, and no stroke of
199
+ // a cross. Taking the first two LINEs outright found a "cross" in any
200
+ // outline -- example009.dxf's fan_top, where the reference finds none.
201
+ // ponytail: LINE and ARC ends only; polylines and splines don't
202
+ // disqualify a LINE here.
203
+ const std::vector<Entity> entities = readEntities(c.path, c.xorigin, c.yorigin, c.scale);
204
+ std::map<std::pair<long long, long long>, int> ends;
205
+ const auto endKey = [](double x, double y) { return std::make_pair(std::llround(x * 1e6), std::llround(y * 1e6)); };
206
+ for (const Entity& e : entities) {
207
+ if (!c.layer.empty() && c.layer != e.layer) continue;
208
+ if (e.type == "LINE" && e.xverts.size() >= 2 && e.yverts.size() >= 2) {
209
+ ++ends[endKey(e.xverts[0], e.yverts[0])];
210
+ ++ends[endKey(e.xverts[1], e.yverts[1])];
211
+ } else if (e.type == "ARC") {
212
+ for (double a : {e.angle, e.angle2}) {
213
+ ++ends[endKey(e.coords[0][0] + e.radius * std::cos(rad(a)), e.coords[0][1] + e.radius * std::sin(rad(a)))];
214
+ }
215
+ }
216
+ }
195
217
  double p[4][2];
196
218
  int found = 0;
197
- for (const Entity& e : readEntities(c.path, c.xorigin, c.yorigin, c.scale)) {
219
+ for (const Entity& e : entities) {
198
220
  if (e.type != "LINE") continue;
199
221
  if (!c.layer.empty() && c.layer != e.layer) continue;
200
222
  if (e.xverts.size() < 2 || e.yverts.size() < 2) continue;
223
+ if (ends[endKey(e.xverts[0], e.yverts[0])] > 1 || ends[endKey(e.xverts[1], e.yverts[1])] > 1) continue;
201
224
  p[found][0] = e.xverts[0];
202
225
  p[found][1] = e.yverts[0];
203
226
  ++found;
@@ -199,8 +199,18 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
199
199
  const std::string path = resolveFilePath(fileArg, node);
200
200
  const std::string ext = lowerExt(path);
201
201
 
202
+ if (!std::filesystem::exists(path)) {
203
+ // A missing file warns and imports nothing, as in OpenSCAD; it
204
+ // aborted the whole render. Warned at generate, where OpenSCAD does,
205
+ // so it follows the script's echoes.
206
+ params["kind"] = Value{std::string("missing")};
207
+ params["path"] = Value{path};
208
+ params["dxf"] = Value{ext == ".dxf"};
209
+ return params;
210
+ }
202
211
  if (ext == ".dxf" || ext == ".svg" || ext == ".pdf") {
203
212
  std::vector<Contour2d> contours;
213
+ bool filteredMiss = false;
204
214
  try {
205
215
  if (ext == ".dxf") {
206
216
  std::optional<std::string> layer;
@@ -211,7 +221,10 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
211
221
  if (const std::string* v = std::get_if<std::string>(&idArg)) filter.id = *v;
212
222
  if (const std::string* v = std::get_if<std::string>(&classArg)) filter.cls = *v;
213
223
  bool matched = true;
214
- contours = loadSvgContours(path, filter, &matched);
224
+ const Value dpiArg = getArg(args, std::nullopt, "dpi", Value{72.0});
225
+ contours = loadSvgContours(path, filter, &matched,
226
+ std::holds_alternative<double>(dpiArg) ? std::get<double>(dpiArg) : 72.0,
227
+ truthy(getArg(args, std::nullopt, "center", Value{false})));
215
228
  if (!matched) {
216
229
  // Nothing imported, rather than silently falling back to
217
230
  // the whole drawing -- a cut layer that quietly became
@@ -222,14 +235,15 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
222
235
  if (!what.empty()) what += ", ";
223
236
  what += "class = \"" + *filter.cls + "\"";
224
237
  }
225
- ev.warn("import() filter " + what + " did not match anything in "
226
- + path, &node.position());
238
+ // OpenSCAD's words, which name no file.
239
+ ev.warn("import() filter " + what + " did not match anything", &node.position());
240
+ filteredMiss = true;
227
241
  }
228
242
  }
229
243
  } catch (const std::exception& e) {
230
244
  ev.error(std::string("import: ") + e.what(), node);
231
245
  }
232
- if (contours.empty()) {
246
+ if (contours.empty() && !filteredMiss) {
233
247
  ev.error(ext == ".dxf" ? "import: no closed contours found in DXF file" : "import: no shapes found in SVG file", node);
234
248
  }
235
249
  params["kind"] = Value{std::string("region")};
@@ -276,6 +290,16 @@ std::vector<ColoredBody> generateImport(Evaluator& ev, const CSGParams& params,
276
290
  const auto kindIt = params.find("kind");
277
291
  if (kindIt == params.end()) return {};
278
292
  const std::string& kind = std::get<std::string>(kindIt->second);
293
+ if (kind == "missing") {
294
+ // OpenSCAD's own wording, which names a line rather than a file.
295
+ const std::string& path = std::get<std::string>(params.at("path"));
296
+ ev.warn(std::get<bool>(params.at("dxf"))
297
+ ? "Can't open DXF file '" + path + "'."
298
+ : "Can't open import file '" + path + "', import() at line " +
299
+ std::to_string(node.position().line),
300
+ nullptr);
301
+ return {};
302
+ }
279
303
  if (kind == "region") {
280
304
  const std::vector<Contour2d> contours = valueToContours(params.at("contours"));
281
305
  manifold::Polygons polys;
@@ -397,6 +421,10 @@ Value importAsValue(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& n
397
421
  }
398
422
  const std::string path = resolveFilePath(fileArg, node);
399
423
  const std::string ext = lowerExt(path);
424
+ if (!std::filesystem::exists(path)) {
425
+ ev.warn("Could not read file '" + path + "'", &node.position());
426
+ return Value{};
427
+ }
400
428
 
401
429
  try {
402
430
  if (ext == ".json") return loadJsonAsValue(path);
@@ -412,7 +440,10 @@ Value importAsValue(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& n
412
440
  if (const std::string* v = std::get_if<std::string>(&idArg)) filter.id = *v;
413
441
  if (const std::string* v = std::get_if<std::string>(&classArg)) filter.cls = *v;
414
442
  bool matched = true;
415
- contours = loadSvgContours(path, filter, &matched);
443
+ const Value dpiArg = getArg(args, std::nullopt, "dpi", Value{72.0});
444
+ contours = loadSvgContours(path, filter, &matched,
445
+ std::holds_alternative<double>(dpiArg) ? std::get<double>(dpiArg) : 72.0,
446
+ truthy(getArg(args, std::nullopt, "center", Value{false})));
416
447
  if (!matched) {
417
448
  // Nothing imported, rather than silently falling back to
418
449
  // the whole drawing -- a cut layer that quietly became
@@ -423,8 +454,7 @@ Value importAsValue(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& n
423
454
  if (!what.empty()) what += ", ";
424
455
  what += "class = \"" + *filter.cls + "\"";
425
456
  }
426
- ev.warn("import() filter " + what + " did not match anything in "
427
- + path, &node.position());
457
+ ev.warn("import() filter " + what + " did not match anything", &node.position());
428
458
  }
429
459
  }
430
460
  return contoursToValue(contours);
@@ -20,23 +20,21 @@ std::optional<std::array<float, 4>> valueToColor(const Value& v) {
20
20
  return c;
21
21
  }
22
22
 
23
- namespace {
24
- // The reference extrudes a 2D shape to exactly 1 unit for preview -- measured
23
+ // kTopLevel2dHeight (colored_body.hpp), the default slab height: the
24
+ // reference extrudes a 2D shape to exactly 1 unit for preview -- measured
25
25
  // against OpenSCAD at three camera tilts (60/70/80 degrees), which put it at
26
26
  // 0.92/1.00/0.99, the 60 being pixel-rounding on the shallowest angle. Ours
27
27
  // was 1e-3, which reads as a flat silhouette with no visible edge and, more
28
28
  // annoyingly, gives --viewall a different bounding box to fit, so every 2D
29
29
  // docs image came out at a different scale from the published one.
30
- constexpr double kTopLevel2dHeight = 1.0;
31
- } // namespace
32
30
 
33
- std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodies) {
31
+ std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodies, double flatHeight) {
34
32
  std::vector<ColoredBody> out;
35
33
  out.reserve(bodies.size());
36
34
  for (const ColoredBody& cb : bodies) {
37
35
  if (!cb.body && cb.section) {
38
36
  ColoredBody flat;
39
- flat.body = manifold::Manifold::Extrude(cb.section->ToPolygons(), kTopLevel2dHeight);
37
+ flat.body = manifold::Manifold::Extrude(cb.section->ToPolygons(), flatHeight);
40
38
  if (cb.sectionId && !flat.body->IsEmpty()) {
41
39
  // One run carrying the section's own ID, so a click on the
42
40
  // slab finds the node that built the shape (see
@@ -7,6 +7,7 @@
7
7
 
8
8
  #include <algorithm>
9
9
  #include <map>
10
+ #include <tuple>
10
11
  #include <unordered_set>
11
12
 
12
13
  namespace oscadeval {
@@ -354,8 +355,22 @@ CoverageResult buildCoverageResult(const std::vector<const ASTNode*>& roots,
354
355
  const std::vector<const ASTNode*>& extraStatements,
355
356
  const CoverageRecorder& recorder) {
356
357
  CoverageResult result;
358
+ // One span per source position. A file several files use<> is parsed
359
+ // once per use, so its nodes arrive once per parse -- BOSL2's
360
+ // builtins.scad three times, two of them with zero hits -- which
361
+ // counted its spans three times over and pulled its percentages down.
362
+ // Merged here, hits summed: each copy's executions are real ones.
363
+ std::map<std::tuple<std::string, int, int, CoverageKind>, size_t> index;
357
364
  for (const Coverable& c : collectCoverable(roots, extraStatements)) {
358
365
  const oscad::Position& p = c.node->position();
366
+ const auto key = std::make_tuple(p.origin, p.start_offset, p.end_offset, c.kind);
367
+ const auto [it, fresh] = index.emplace(key, result.spans.size());
368
+ if (!fresh) {
369
+ CoverageSpan& s = result.spans[it->second];
370
+ s.hits += recorder.hitsFor(*c.node);
371
+ s.arm = s.arm || c.arm;
372
+ continue;
373
+ }
359
374
  result.spans.push_back(CoverageSpan{p.origin, p.line, p.column, p.start_offset, p.end_offset, c.kind, c.arm,
360
375
  recorder.hitsFor(*c.node)});
361
376
  }
@@ -663,7 +663,11 @@ void writePly(const std::string& path, const std::vector<ExportObject>& objects)
663
663
  colors.push_back(rgb(o.triColors[t], 0));
664
664
  colors.push_back(rgb(o.triColors[t], 1));
665
665
  colors.push_back(rgb(o.triColors[t], 2));
666
- faces.push_back(static_cast<int32_t>(faces.size()) + base);
666
+ // The vertex just pushed. Not faces.size() + base: faces
667
+ // already holds every earlier object's entries, so that
668
+ // counted `base` twice and pointed past the vertex list
669
+ // on any object after the first.
670
+ faces.push_back(static_cast<int32_t>(verts.size() / 3) - 1);
667
671
  }
668
672
  }
669
673
  }
@@ -5,6 +5,7 @@
5
5
  #include <cmath>
6
6
  #include <cstring>
7
7
  #include <fstream>
8
+ #include <optional>
8
9
  #include <sstream>
9
10
  #include <stdexcept>
10
11
 
@@ -194,7 +195,7 @@ Mat3 compose(const Mat3& m, const Mat3& acc) {
194
195
  std::array<double, 2> applyMat(const std::array<double, 2>& pt, const Mat3& m) {
195
196
  const double x = m.a * pt[0] + m.c * pt[1] + m.e;
196
197
  const double y = m.b * pt[0] + m.d * pt[1] + m.f;
197
- return {x, -y}; // flip Y: SVG down -> OpenSCAD up
198
+ return {x, y}; // SVG user units; pageMap places them on the page
198
199
  }
199
200
 
200
201
  std::vector<double> parseNumberList(const std::string& s) {
@@ -489,11 +490,109 @@ void walkFiltered(const XmlNode& el, const Mat3& mat, const SvgFilter& f,
489
490
  for (const XmlNode& child : el.children) walkFiltered(child, m, f, out, matched);
490
491
  }
491
492
 
493
+ // "12", "12.5mm", "3in" -> (number, unit); unit empty when unitless.
494
+ // nullopt for anything else, which OpenSCAD treats as absent.
495
+ std::optional<std::pair<double, std::string>> parseLength(const std::string& text) {
496
+ const size_t b = text.find_first_not_of(" \t\r\n");
497
+ if (b == std::string::npos) return std::nullopt;
498
+ const size_t e = text.find_last_not_of(" \t\r\n");
499
+ const std::string t = text.substr(b, e - b + 1);
500
+ size_t used = 0;
501
+ double n = 0.0;
502
+ try {
503
+ n = std::stod(t, &used);
504
+ } catch (...) {
505
+ return std::nullopt;
506
+ }
507
+ std::string unit = t.substr(used);
508
+ unit.erase(0, unit.find_first_not_of(" \t"));
509
+ static const char* known[] = {"", "em", "ex", "px", "in", "cm", "mm", "pt", "pc", "%"};
510
+ for (const char* k : known) {
511
+ if (unit == k) return std::make_pair(n, unit);
512
+ }
513
+ return std::nullopt;
514
+ }
515
+
516
+ // Place SVG user-unit contours as OpenSCAD's import_svg.cc does: the page's
517
+ // width/height to millimetres (a unitless length at `dpi`, px at 96), the
518
+ // viewBox scaled onto it under preserveAspectRatio (default xMidYMid meet),
519
+ // and Y flipped about the page height -- or, with center, about the
520
+ // drawing's own centre. Without this a unitless 100-unit drawing came in
521
+ // 2.8x too large and below the X axis.
522
+ void pageMap(const XmlNode& root, double dpi, bool center, std::vector<Contour2d>& contours) {
523
+ std::vector<double> vb;
524
+ {
525
+ std::string v = root.getAttr("viewBox");
526
+ std::replace(v.begin(), v.end(), ',', ' ');
527
+ std::istringstream in(v);
528
+ double x = 0.0;
529
+ while (in >> x) vb.push_back(x);
530
+ }
531
+ const bool valid = vb.size() == 4 && vb[2] >= 0.0 && vb[3] >= 0.0;
532
+ const auto toMm = [&](const char* attr, double viewbox) {
533
+ const auto len = parseLength(root.getAttr(attr));
534
+ if (!len) return valid ? 25.4 * viewbox / dpi : 0.0; // absent: rely on dpi, as old Illustrator files do
535
+ const auto& [n, u] = *len;
536
+ if (u.empty()) return 25.4 * n / dpi;
537
+ if (u == "px") return 25.4 * n / 96.0;
538
+ if (u == "pt") return 25.4 * n / 72.0;
539
+ if (u == "pc") return 25.4 * n / 6.0;
540
+ if (u == "in") return 25.4 * n;
541
+ if (u == "cm") return 10.0 * n;
542
+ if (u == "mm") return n;
543
+ if (u == "%") return valid ? 25.4 * n / 100.0 * viewbox / dpi : 0.0;
544
+ return valid ? viewbox : 0.0; // em, ex
545
+ };
546
+ const double widthMm = toMm("width", valid ? vb[2] : 0.0);
547
+ const double heightMm = toMm("height", valid ? vb[3] : 0.0);
548
+ double sx = 1.0, sy = 1.0, vbx = 0.0, vby = 0.0, ax = 0.0, ay = 0.0;
549
+ if (valid) {
550
+ const auto w = parseLength(root.getAttr("width")), h = parseLength(root.getAttr("height"));
551
+ vbx = vb[0] * (w && w->second == "%" ? w->first / 100.0 : 1.0);
552
+ vby = vb[1] * (h && h->second == "%" ? h->first / 100.0 : 1.0);
553
+ sx = vb[2] != 0.0 ? widthMm / vb[2] : 0.0;
554
+ sy = vb[3] != 0.0 ? heightMm / vb[3] : 0.0;
555
+ std::istringstream par(root.getAttr("preserveAspectRatio"));
556
+ std::string align, meet;
557
+ par >> align;
558
+ if (align == "defer") par >> align;
559
+ par >> meet;
560
+ if (align.empty()) align = "xMidYMid";
561
+ if (align != "none") {
562
+ const double scaling = meet == "slice" ? std::max(sx, sy) : std::min(sx, sy);
563
+ sx = sy = scaling;
564
+ const auto frac = [](const std::string& a) { return a == "Min" ? 0.0 : a == "Max" ? 1.0 : 0.5; };
565
+ const bool wellFormed = align.size() == 8 && align[0] == 'x' && align[4] == 'Y';
566
+ const double fx = wellFormed ? frac(align.substr(1, 3)) : 0.5;
567
+ const double fy = wellFormed ? frac(align.substr(5, 3)) : 0.5;
568
+ ax = fx * (widthMm - sx * vb[2]);
569
+ ay = fy * (heightMm - sy * vb[3]);
570
+ }
571
+ }
572
+ double cx = -ax, cy = heightMm - ay;
573
+ if (center) {
574
+ double lo[2] = {INFINITY, INFINITY}, hi[2] = {-INFINITY, -INFINITY};
575
+ for (const Contour2d& c : contours)
576
+ for (const auto& p : c) {
577
+ lo[0] = std::min(lo[0], sx * p[0]);
578
+ hi[0] = std::max(hi[0], sx * p[0]);
579
+ lo[1] = std::min(lo[1], sy * p[1]);
580
+ hi[1] = std::max(hi[1], sy * p[1]);
581
+ }
582
+ cx = std::isfinite(lo[0]) ? (lo[0] + hi[0]) / 2.0 : 0.0;
583
+ cy = std::isfinite(lo[1]) ? (lo[1] + hi[1]) / 2.0 : 0.0;
584
+ }
585
+ // -vby - y, not y - vby: OpenSCAD's own formula, kept for parity.
586
+ for (Contour2d& c : contours)
587
+ for (auto& p : c) p = {sx * (p[0] - vbx) - cx, sy * (-vby - p[1]) + cy};
588
+ }
589
+
492
590
  } // namespace
493
591
 
494
592
  std::vector<Contour2d> loadSvgContours(const std::string& path,
495
593
  const SvgFilter& filter,
496
- bool* matched) {
594
+ bool* matched, double dpi, bool center) {
595
+ if (!(dpi > 0.0)) dpi = 72.0;
497
596
  std::ifstream in(path);
498
597
  if (!in) throw std::runtime_error("could not open '" + path + "'");
499
598
  std::stringstream buf;
@@ -505,6 +604,7 @@ std::vector<Contour2d> loadSvgContours(const std::string& path,
505
604
  if (!filter.id && !filter.cls) {
506
605
  if (matched) *matched = true; // no filter, nothing to miss
507
606
  walk(*root, Mat3{}, out);
607
+ pageMap(*root, dpi, center, out);
508
608
  return out;
509
609
  }
510
610
  bool hit = false;
@@ -513,6 +613,7 @@ std::vector<Contour2d> loadSvgContours(const std::string& path,
513
613
  // back to the whole drawing, which would be the silent-wrong-geometry
514
614
  // answer. The caller turns this into the warning.
515
615
  if (matched) *matched = hit;
616
+ pageMap(*root, dpi, center, out);
516
617
  return out;
517
618
  }
518
619