openscad-cpp-evaluator 1.21.0__tar.gz → 1.22.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.21.0 → openscad_cpp_evaluator-1.22.0}/CLAUDE.md +12 -0
  2. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +15 -1
  4. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/pyproject.toml +1 -1
  5. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/function_builtins.cpp +1 -0
  6. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/import.cpp +50 -2
  7. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/registry.cpp +5 -2
  8. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/import/svg_import.cpp +53 -2
  9. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_dxf_svg_import.cpp +102 -0
  10. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_feature_detection.cpp +2 -1
  11. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_python_bindings.py +54 -0
  12. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/.github/workflows/ci.yml +0 -0
  13. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/.github/workflows/wheels.yml +0 -0
  14. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/.gitignore +0 -0
  15. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/.gitmodules +0 -0
  16. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/CMakeLists.txt +0 -0
  17. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/LICENSE +0 -0
  18. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/README.md +0 -0
  19. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/bindings/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/bindings/ast_to_py.cpp +0 -0
  21. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/bindings/module.cpp +0 -0
  22. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/cmake/embed_font.cmake +0 -0
  23. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/examples/CMakeLists.txt +0 -0
  24. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/examples/eval_perf_benchmark.cpp +0 -0
  25. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/examples/manifold_cache_reuse.cpp +0 -0
  26. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/examples/minimal_debugger.cpp +0 -0
  27. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  28. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/.gitignore +0 -0
  29. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  30. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  31. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/README.md +0 -0
  32. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  33. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  34. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  35. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  36. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  37. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  38. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  39. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  40. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  41. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  43. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  44. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  45. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  46. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  47. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  48. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  49. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  50. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  51. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  52. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  53. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  54. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  55. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  56. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  57. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  58. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  59. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  60. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  61. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  62. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  63. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  64. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  65. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  66. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  67. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  68. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  69. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  70. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  71. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  72. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  73. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  74. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  75. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  76. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  77. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  78. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  79. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  80. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  81. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  82. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  83. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  84. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  85. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  86. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  87. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  88. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  89. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  90. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  91. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  92. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  93. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  94. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  95. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  96. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  97. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  98. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  99. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  100. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  101. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  102. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  103. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  104. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  105. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  106. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  107. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  108. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  109. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  110. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  111. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  112. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  113. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  114. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  116. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  117. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  118. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  119. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  120. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  121. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  122. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  123. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  124. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  125. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  126. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  127. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  128. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  129. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  130. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  131. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  132. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LICENSE +0 -0
  133. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  134. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  135. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  136. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  137. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/CMakeLists.txt +0 -0
  138. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/booleans.cpp +0 -0
  139. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/builtins.hpp +0 -0
  140. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/call_args.cpp +0 -0
  141. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/color.cpp +0 -0
  142. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/control.cpp +0 -0
  143. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/dxf_dim.cpp +0 -0
  144. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/extrude.cpp +0 -0
  145. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/modifiers.cpp +0 -0
  146. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/primitives_2d.cpp +0 -0
  147. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/primitives_3d.cpp +0 -0
  148. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/roof.cpp +0 -0
  149. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/segments.cpp +0 -0
  150. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/surface.cpp +0 -0
  151. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/text.cpp +0 -0
  152. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/topology.cpp +0 -0
  153. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/builtins/transforms.cpp +0 -0
  154. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/bytecode_compiler.cpp +0 -0
  155. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/bytecode_vm.cpp +0 -0
  156. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/colored_body.cpp +0 -0
  157. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/coverage.cpp +0 -0
  158. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/csg_generate.cpp +0 -0
  159. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/csg_resolve.cpp +0 -0
  160. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/css_colors.cpp +0 -0
  161. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/debug/debug_repl.cpp +0 -0
  162. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/debug_profile.cpp +0 -0
  163. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/eval_context.cpp +0 -0
  164. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/eval_error.cpp +0 -0
  165. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/eval_use.cpp +0 -0
  166. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/export.cpp +0 -0
  167. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/expr_eval.cpp +0 -0
  168. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/format_closure.cpp +0 -0
  169. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/import/dxf_import.cpp +0 -0
  170. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/import/mesh_import.cpp +0 -0
  171. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/import/surface_load.cpp +0 -0
  172. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/manifold_cache.cpp +0 -0
  173. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/measure_geometry.cpp +0 -0
  174. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/mesh_check.cpp +0 -0
  175. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/stmt_eval.cpp +0 -0
  176. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/text/font_match.cpp +0 -0
  177. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/text/freetype_font_provider.cpp +0 -0
  178. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/text/text_metrics.cpp +0 -0
  179. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/user_calls.cpp +0 -0
  180. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/utf8.cpp +0 -0
  181. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/value.cpp +0 -0
  182. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/src/zip_stored.cpp +0 -0
  183. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/CMakeLists.txt +0 -0
  184. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_booleans.cpp +0 -0
  185. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_bytecode_compiler.cpp +0 -0
  186. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_children_separate.cpp +0 -0
  187. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_cli.cpp +0 -0
  188. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_control_flow.cpp +0 -0
  189. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_coverage.cpp +0 -0
  190. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_csg_tree.cpp +0 -0
  191. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_debug_hooks.cpp +0 -0
  192. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_eval_context.cpp +0 -0
  193. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_export.cpp +0 -0
  194. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_expr_eval.cpp +0 -0
  195. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_extrude_roof.cpp +0 -0
  196. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_function_builtins.cpp +0 -0
  197. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_helpers.hpp +0 -0
  198. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_import_export.cpp +0 -0
  199. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_include_cache.cpp +0 -0
  200. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_manifold_cache.cpp +0 -0
  201. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_mesh_check.cpp +0 -0
  202. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_multi_color_merge.cpp +0 -0
  203. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_primitives.cpp +0 -0
  204. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_profiling.cpp +0 -0
  205. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_render_expr.cpp +0 -0
  206. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_scope_trail.cpp +0 -0
  207. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_scoping.cpp +0 -0
  208. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_surface.cpp +0 -0
  209. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_tail_calls.cpp +0 -0
  210. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_text.cpp +0 -0
  211. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_transforms.cpp +0 -0
  212. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_unexpected_args.cpp +0 -0
  213. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_use.cpp +0 -0
  214. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_value.cpp +0 -0
  215. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_viewport_params.cpp +0 -0
  216. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tests/test_zip_stored.cpp +0 -0
  217. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tools/cli/CMakeLists.txt +0 -0
  218. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tools/cli/cli_lib.cpp +0 -0
  219. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tools/cli/cli_lib.hpp +0 -0
  220. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/tools/cli/main.cpp +0 -0
  221. {openscad_cpp_evaluator-1.21.0 → openscad_cpp_evaluator-1.22.0}/uv.lock +0 -0
@@ -1121,6 +1121,18 @@ grep for `ponytail:`.
1121
1121
  `user_calls.cpp`'s `evalFunctionCall`, special-cased *before* the `isBuiltinFunctionName` gate
1122
1122
  since `import` isn't in that table — returns a VNF `[[verts],[faces]]` for a mesh file, a Region
1123
1123
  `[[[x,y],...],...]` for DXF/SVG, or the JSON file's content as native values).
1124
+ **SVG filtering**: `id=` selects one element by its `id` (upstream's own parameter, and
1125
+ matching a `<g>` takes everything under it); `class=` selects by one entry of an element's
1126
+ space-separated `class` list, which is **this port's addition** (`supported_feature("svg-class")`)
1127
+ because a class is how a drawing marks "every cut line" without naming each one. Both apply in
1128
+ the module *and* expression forms, enclosing transforms still apply to a selected element (it
1129
+ has to land where it does in the drawing), and **a filter that matches nothing imports nothing
1130
+ and warns** rather than falling back to the whole file — a cut layer that quietly became every
1131
+ layer is the worst answer available. `layer=` stays DXF-only on purpose: upstream reuses the
1132
+ name for SVG but reads only Inkscape's `inkscape:label`, a vendor convention rather than
1133
+ anything SVG defines, so it cannot match a layer from any other tool. `id` had been in
1134
+ `registry.cpp`'s declared parameter list since long before anything read it, so a script
1135
+ filtering by id was accepted in full and silently handed the whole drawing.
1124
1136
  `resolveFilePath()` (also declared here, defined in import.cpp) — resolves a file argument
1125
1137
  relative to the *source .scad file's own directory* — is shared by every file-reading builtin,
1126
1138
  not just import() (`surface.cpp` uses it too). `zip_stored.hpp` is a hand-rolled, dependency-free
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.21.0
3
+ Version: 1.22.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -22,6 +22,20 @@ std::vector<Contour2d> loadDxfContours(const std::string& path, const std::optio
22
22
  // group transforms). Y is flipped (SVG's is down, OpenSCAD's is up).
23
23
  // Throws std::runtime_error on I/O/parse failure. Mirrors
24
24
  // _load_svg_contours.
25
- std::vector<Contour2d> loadSvgContours(const std::string& path);
25
+ //: Which part of the drawing to import. Both unset means the whole file.
26
+ //: `id` matches an element's `id` attribute; `cls` matches one entry of its
27
+ //: space-separated `class` list. Matching an element takes everything under
28
+ //: it, so a filter naming a <g> means "that group".
29
+ struct SvgFilter {
30
+ std::optional<std::string> id;
31
+ std::optional<std::string> cls;
32
+ };
33
+
34
+ // `matched`, when given, reports whether the filter found anything. A miss
35
+ // imports nothing rather than falling back to the whole drawing; the caller
36
+ // warns. Always true when no filter was set.
37
+ std::vector<Contour2d> loadSvgContours(const std::string& path,
38
+ const SvgFilter& filter = {},
39
+ bool* matched = nullptr);
26
40
 
27
41
  } // 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.21.0"
7
+ version = "1.22.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -1046,6 +1046,7 @@ const std::unordered_map<std::string, double>& featureLevels() {
1046
1046
  {"expr-import", 1.0}, // import() in expression position
1047
1047
  {"object-function", 1.0}, // object(), unconditional here
1048
1048
  {"roof-op", 1.0}, // roof(), method="voronoi" only
1049
+ {"svg-class", 1.0}, // import(svg, class=); id= is upstream's
1049
1050
  };
1050
1051
  return levels;
1051
1052
  }
@@ -176,6 +176,13 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
176
176
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
177
177
  const Value fileArg = getArg(args, 0, "file", Value{});
178
178
  const Value layerArg = getArg(args, std::nullopt, "layer", Value{});
179
+ // SVG only. `id` matches an element's id; `class` matches one entry of
180
+ // its space-separated class list. `layer` stays DXF's, where layers are
181
+ // part of the format -- upstream reuses the name for SVG but reads only
182
+ // Inkscape's `inkscape:label`, which is a vendor convention rather than
183
+ // anything SVG defines.
184
+ const Value idArg = getArg(args, std::nullopt, "id", Value{});
185
+ const Value classArg = getArg(args, std::nullopt, "class", Value{});
179
186
  // Not an OpenSCAD parameter. A script using it will not run upstream,
180
187
  // which is why it has to be asked for rather than being the default.
181
188
  const bool repair = truthy(getArg(args, std::nullopt, "repair", Value{false}));
@@ -196,7 +203,24 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
196
203
  if (const std::string* s = std::get_if<std::string>(&layerArg)) layer = *s;
197
204
  contours = loadDxfContours(path, layer);
198
205
  } else {
199
- contours = loadSvgContours(path);
206
+ SvgFilter filter;
207
+ if (const std::string* v = std::get_if<std::string>(&idArg)) filter.id = *v;
208
+ if (const std::string* v = std::get_if<std::string>(&classArg)) filter.cls = *v;
209
+ bool matched = true;
210
+ contours = loadSvgContours(path, filter, &matched);
211
+ if (!matched) {
212
+ // Nothing imported, rather than silently falling back to
213
+ // the whole drawing -- a cut layer that quietly became
214
+ // every layer is the worst answer available.
215
+ std::string what;
216
+ if (filter.id) what += "id = \"" + *filter.id + "\"";
217
+ if (filter.cls) {
218
+ if (!what.empty()) what += ", ";
219
+ what += "class = \"" + *filter.cls + "\"";
220
+ }
221
+ ev.warn("import() filter " + what + " did not match anything in "
222
+ + path, &node.position());
223
+ }
200
224
  }
201
225
  } catch (const std::exception& e) {
202
226
  ev.error(std::string("import: ") + e.what(), node);
@@ -340,6 +364,13 @@ std::vector<ColoredBody> generateImport(Evaluator& ev, const CSGParams& params,
340
364
  Value importAsValue(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& node) {
341
365
  const Value fileArg = getArg(args, 0, "file", Value{});
342
366
  const Value layerArg = getArg(args, std::nullopt, "layer", Value{});
367
+ // SVG only. `id` matches an element's id; `class` matches one entry of
368
+ // its space-separated class list. `layer` stays DXF's, where layers are
369
+ // part of the format -- upstream reuses the name for SVG but reads only
370
+ // Inkscape's `inkscape:label`, which is a vendor convention rather than
371
+ // anything SVG defines.
372
+ const Value idArg = getArg(args, std::nullopt, "id", Value{});
373
+ const Value classArg = getArg(args, std::nullopt, "class", Value{});
343
374
  if (std::holds_alternative<std::monostate>(fileArg)) {
344
375
  ev.error("import: 'file' parameter is required", node);
345
376
  }
@@ -356,7 +387,24 @@ Value importAsValue(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& n
356
387
  if (const std::string* s = std::get_if<std::string>(&layerArg)) layer = *s;
357
388
  contours = loadDxfContours(path, layer);
358
389
  } else {
359
- contours = loadSvgContours(path);
390
+ SvgFilter filter;
391
+ if (const std::string* v = std::get_if<std::string>(&idArg)) filter.id = *v;
392
+ if (const std::string* v = std::get_if<std::string>(&classArg)) filter.cls = *v;
393
+ bool matched = true;
394
+ contours = loadSvgContours(path, filter, &matched);
395
+ if (!matched) {
396
+ // Nothing imported, rather than silently falling back to
397
+ // the whole drawing -- a cut layer that quietly became
398
+ // every layer is the worst answer available.
399
+ std::string what;
400
+ if (filter.id) what += "id = \"" + *filter.id + "\"";
401
+ if (filter.cls) {
402
+ if (!what.empty()) what += ", ";
403
+ what += "class = \"" + *filter.cls + "\"";
404
+ }
405
+ ev.warn("import() filter " + what + " did not match anything in "
406
+ + path, &node.position());
407
+ }
360
408
  }
361
409
  return contoursToValue(contours);
362
410
  }
@@ -116,10 +116,13 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
116
116
  {"minkowski", {"convexity"}},
117
117
  {"children", {"index", "separate"}},
118
118
  {"render", {"convexity"}},
119
- // "repair" is this port's own addition, not an upstream parameter.
119
+ // "repair" and "class" are this port's own additions, not upstream
120
+ // parameters. ("id" IS upstream's, and was declared here long before
121
+ // anything read it -- so a script filtering by id was accepted in
122
+ // full and quietly given the whole drawing.)
120
123
  {"import",
121
124
  {"file", "layer", "convexity", "origin", "scale", "width", "height", "filename", "layername", "center", "dpi",
122
- "id", "repair"}},
125
+ "id", "class", "repair"}},
123
126
  {"linear_extrude", {"height", "v", "scale", "center", "twist", "slices", "segments", "convexity"}},
124
127
  {"rotate_extrude", {"angle", "start", "convexity"}},
125
128
  {"projection", {"cut", "convexity"}},
@@ -450,9 +450,50 @@ void walk(const XmlNode& el, const Mat3& mat, std::vector<Contour2d>& out) {
450
450
  for (const XmlNode& child : el.children) walk(child, m, out);
451
451
  }
452
452
 
453
+ // `class` is a space-separated LIST, so this is membership rather than
454
+ // equality: class="cut outline" is matched by class="cut".
455
+ bool hasClass(const XmlNode& el, const std::string& want) {
456
+ const std::string attr = el.getAttr("class");
457
+ size_t i = 0;
458
+ while (i < attr.size()) {
459
+ while (i < attr.size() && std::isspace(static_cast<unsigned char>(attr[i]))) ++i;
460
+ size_t j = i;
461
+ while (j < attr.size() && !std::isspace(static_cast<unsigned char>(attr[j]))) ++j;
462
+ if (j > i && attr.compare(i, j - i, want) == 0) return true;
463
+ i = j;
464
+ }
465
+ return false;
466
+ }
467
+
468
+ bool matches(const XmlNode& el, const SvgFilter& f) {
469
+ if (f.id && el.getAttr("id") == *f.id) return true;
470
+ if (f.cls && hasClass(el, *f.cls)) return true;
471
+ return false;
472
+ }
473
+
474
+ // Walk looking for matching elements; everything under one is taken whole,
475
+ // which is what makes `id=` on a <g> mean "that group". Transforms above
476
+ // the match still apply -- a selected shape must land where it does in the
477
+ // drawing, not at the origin.
478
+ void walkFiltered(const XmlNode& el, const Mat3& mat, const SvgFilter& f,
479
+ std::vector<Contour2d>& out, bool& matched) {
480
+ if (el.tag == "defs" || el.tag == "symbol") return;
481
+ const Mat3 m = compose(parseTransform(el.getAttr("transform")), mat);
482
+ if (matches(el, f)) {
483
+ matched = true;
484
+ const std::vector<Contour2d> shapes = shapeContours(el, m);
485
+ out.insert(out.end(), shapes.begin(), shapes.end());
486
+ for (const XmlNode& child : el.children) walk(child, m, out);
487
+ return; // a match is taken whole; no nested re-matching
488
+ }
489
+ for (const XmlNode& child : el.children) walkFiltered(child, m, f, out, matched);
490
+ }
491
+
453
492
  } // namespace
454
493
 
455
- std::vector<Contour2d> loadSvgContours(const std::string& path) {
494
+ std::vector<Contour2d> loadSvgContours(const std::string& path,
495
+ const SvgFilter& filter,
496
+ bool* matched) {
456
497
  std::ifstream in(path);
457
498
  if (!in) throw std::runtime_error("could not open '" + path + "'");
458
499
  std::stringstream buf;
@@ -461,7 +502,17 @@ std::vector<Contour2d> loadSvgContours(const std::string& path) {
461
502
  if (!root) throw std::runtime_error("'" + path + "' is not a well-formed SVG file");
462
503
 
463
504
  std::vector<Contour2d> out;
464
- walk(*root, Mat3{}, out);
505
+ if (!filter.id && !filter.cls) {
506
+ if (matched) *matched = true; // no filter, nothing to miss
507
+ walk(*root, Mat3{}, out);
508
+ return out;
509
+ }
510
+ bool hit = false;
511
+ walkFiltered(*root, Mat3{}, filter, out, hit);
512
+ // A filter that matched nothing imports nothing -- it does NOT fall
513
+ // back to the whole drawing, which would be the silent-wrong-geometry
514
+ // answer. The caller turns this into the warning.
515
+ if (matched) *matched = hit;
465
516
  return out;
466
517
  }
467
518
 
@@ -550,3 +550,105 @@ TEST(DxfCross, ParallelOrTooFewLinesWarns) {
550
550
  EXPECT_NE(last.find("Can't find cross"), std::string::npos) << last;
551
551
  std::filesystem::remove(path);
552
552
  }
553
+
554
+ // --- SVG id=/class= filters ------------------------------------------------
555
+ //
556
+ // Upstream OpenSCAD filters an SVG import by `id`. `class` is this port's own
557
+ // addition (`supported_feature("svg-class")`), because a class is how a
558
+ // drawing marks "every cut line" without naming each one.
559
+ //
560
+ // `layer` deliberately stays DXF's: upstream reuses the name for SVG but
561
+ // reads only Inkscape's `inkscape:label`, a vendor convention rather than
562
+ // anything SVG defines, so it would not match a layer from any other tool.
563
+
564
+ namespace {
565
+
566
+ constexpr const char* kFilterSvg = R"SVG(<svg xmlns="http://www.w3.org/2000/svg"
567
+ width="100" height="100" viewBox="0 0 100 100">
568
+ <g id="grp" transform="translate(10,0)">
569
+ <rect id="inner" class="cut outline" x="0" y="0" width="20" height="10"/>
570
+ </g>
571
+ <rect id="other" class="outline" x="50" y="50" width="10" height="10"/>
572
+ </svg>)SVG";
573
+
574
+ size_t contourCount(const std::string& file, const std::string& args, Evaluator& ev) {
575
+ Value v = asExpr("import(\"" + file + "\"" + args + ")", ev);
576
+ return std::get<ListPtr>(v)->items.size();
577
+ }
578
+
579
+ } // namespace
580
+
581
+ TEST(SvgFilter, NoFilterImportsEverything) {
582
+ const auto path = tempPath("filter_all.svg");
583
+ writeFile(path, kFilterSvg);
584
+ Evaluator ev;
585
+ EXPECT_EQ(contourCount(path.generic_string(), "", ev), 2u);
586
+ std::filesystem::remove(path);
587
+ }
588
+
589
+ TEST(SvgFilter, IdSelectsOneElement) {
590
+ const auto path = tempPath("filter_id.svg");
591
+ writeFile(path, kFilterSvg);
592
+ Evaluator ev;
593
+ EXPECT_EQ(contourCount(path.generic_string(), ", id=\"inner\"", ev), 1u);
594
+ EXPECT_EQ(contourCount(path.generic_string(), ", id=\"other\"", ev), 1u);
595
+ std::filesystem::remove(path);
596
+ }
597
+
598
+ TEST(SvgFilter, IdOnAGroupTakesItsContents) {
599
+ const auto path = tempPath("filter_grp.svg");
600
+ writeFile(path, kFilterSvg);
601
+ Evaluator ev;
602
+ EXPECT_EQ(contourCount(path.generic_string(), ", id=\"grp\"", ev), 1u);
603
+ std::filesystem::remove(path);
604
+ }
605
+
606
+ TEST(SvgFilter, ClassIsListMembershipNotEquality) {
607
+ // class="cut outline" is matched by either name on its own.
608
+ const auto path = tempPath("filter_cls.svg");
609
+ writeFile(path, kFilterSvg);
610
+ Evaluator ev;
611
+ EXPECT_EQ(contourCount(path.generic_string(), ", class=\"cut\"", ev), 1u);
612
+ EXPECT_EQ(contourCount(path.generic_string(), ", class=\"outline\"", ev), 2u);
613
+ // A prefix of a real class must not match.
614
+ EXPECT_EQ(contourCount(path.generic_string(), ", class=\"out\"", ev), 0u);
615
+ std::filesystem::remove(path);
616
+ }
617
+
618
+ TEST(SvgFilter, AnEnclosingTransformStillApplies) {
619
+ // The selected rect sits inside translate(10,0). Picking it must not
620
+ // move it back to the origin -- it has to land where it does in the
621
+ // drawing, or a filtered import cannot be composed with an unfiltered one.
622
+ const auto path = tempPath("filter_xf.svg");
623
+ writeFile(path, kFilterSvg);
624
+ Evaluator ev;
625
+ Value v = asExpr("import(\"" + path.generic_string() + "\", id=\"inner\")", ev);
626
+ const auto& region = std::get<ListPtr>(v)->items;
627
+ ASSERT_EQ(region.size(), 1u);
628
+ const auto& pts = std::get<ListPtr>(region[0])->items;
629
+ ASSERT_EQ(pts.size(), 4u);
630
+ double minx = 1e9;
631
+ for (const auto& p : pts) minx = std::min(minx, std::get<double>(std::get<ListPtr>(p)->items[0]));
632
+ EXPECT_NEAR(minx, 10.0, 1e-9);
633
+ std::filesystem::remove(path);
634
+ }
635
+
636
+ TEST(SvgFilter, AMissImportsNothingAndWarns) {
637
+ // Emphatically not a fall-back to the whole drawing: a cut layer that
638
+ // quietly became every layer is the worst answer available.
639
+ const auto path = tempPath("filter_miss.svg");
640
+ writeFile(path, kFilterSvg);
641
+ std::string last;
642
+ Evaluator ev([&](const std::string& m) { last = m; });
643
+ EXPECT_EQ(contourCount(path.generic_string(), ", id=\"nosuch\"", ev), 0u);
644
+ EXPECT_NE(last.find("did not match anything"), std::string::npos) << last;
645
+ std::filesystem::remove(path);
646
+ }
647
+
648
+ TEST(SvgFilter, IdAndClassTogetherMatchEither) {
649
+ const auto path = tempPath("filter_both.svg");
650
+ writeFile(path, kFilterSvg);
651
+ Evaluator ev;
652
+ EXPECT_EQ(contourCount(path.generic_string(), ", id=\"other\", class=\"cut\"", ev), 2u);
653
+ std::filesystem::remove(path);
654
+ }
@@ -35,7 +35,8 @@ std::vector<std::string> echoesFrom(const std::string& src) {
35
35
  // in featureLevels() -- shows up as a 0.
36
36
  const char* kFeatures[] = {"separate-children", "minkowski-diff", "sphere-styles", "export-name",
37
37
  "simplify-op", "expr-import", "object-function", "roof-op",
38
- "render-expr", "polyhedron-vnf", "linear-solve", "levelset"};
38
+ "render-expr", "polyhedron-vnf", "linear-solve", "levelset",
39
+ "svg-class"};
39
40
 
40
41
  } // namespace
41
42
 
@@ -992,3 +992,57 @@ def test_top_level_geometry_has_no_call_site():
992
992
  for node in id_to_node.values():
993
993
  assert node.call_site is None
994
994
  assert os.path.realpath(node.position.origin) == os.path.realpath(p)
995
+
996
+
997
+ SVG_FILTER = """<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
998
+ <g id="grp" transform="translate(10,0)">
999
+ <rect id="inner" class="cut outline" x="0" y="0" width="20" height="10"/>
1000
+ </g>
1001
+ <rect id="other" class="outline" x="50" y="50" width="10" height="10"/>
1002
+ </svg>"""
1003
+
1004
+
1005
+ def _svg_counts(tmp_path, args):
1006
+ """(count, messages) for the region import() returns.
1007
+
1008
+ echo_fn receives warnings as well as echoes, so the ECHO line has to be
1009
+ picked out rather than assumed to be first -- a filter that misses warns
1010
+ before it echoes."""
1011
+ svg = tmp_path / "f.svg"
1012
+ svg.write_text(SVG_FILTER)
1013
+ scad = tmp_path / "f.scad"
1014
+ scad.write_text(f'echo(n = len(import("{svg}"{args})));\n')
1015
+ msgs = []
1016
+ Evaluator(echo_fn=msgs.append).evaluate(str(scad), generate=False)
1017
+ echoes = [m for m in msgs if m.startswith("ECHO:")]
1018
+ assert len(echoes) == 1, msgs
1019
+ return int(echoes[0].split("=")[1]), msgs
1020
+
1021
+
1022
+ def test_svg_import_filters_by_id(tmp_path):
1023
+ assert _svg_counts(tmp_path, "")[0] == 2
1024
+ assert _svg_counts(tmp_path, ', id = "inner"')[0] == 1
1025
+ assert _svg_counts(tmp_path, ', id = "grp"')[0] == 1
1026
+
1027
+
1028
+ def test_svg_import_filters_by_class(tmp_path):
1029
+ """A class is a space-separated list, so this is membership."""
1030
+ assert _svg_counts(tmp_path, ', class = "cut"')[0] == 1
1031
+ assert _svg_counts(tmp_path, ', class = "outline"')[0] == 2
1032
+ assert _svg_counts(tmp_path, ', class = "out"')[0] == 0
1033
+
1034
+
1035
+ def test_svg_filter_miss_imports_nothing_and_warns(tmp_path):
1036
+ """Not a fall-back to the whole drawing -- that would be wrong geometry
1037
+ with nothing said about it."""
1038
+ count, msgs = _svg_counts(tmp_path, ', id = "nosuch"')
1039
+ assert count == 0
1040
+ assert any("did not match anything" in m for m in msgs), msgs
1041
+
1042
+
1043
+ def test_svg_class_is_feature_detectable(tmp_path):
1044
+ scad = tmp_path / "f.scad"
1045
+ scad.write_text('echo(v = supported_feature("svg-class"));\n')
1046
+ out = []
1047
+ Evaluator(echo_fn=out.append).evaluate(str(scad), generate=False)
1048
+ assert "1" in out[0], out