openscad-cpp-evaluator 1.20.5__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 (222) hide show
  1. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/.gitignore +2 -0
  2. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/CLAUDE.md +51 -1
  3. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/PKG-INFO +1 -1
  4. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/bindings/module.cpp +31 -3
  5. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/csg_node.hpp +7 -0
  6. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +15 -1
  7. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/evaluator.hpp +83 -1
  8. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/pyproject.toml +1 -1
  9. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/python/openscad_cpp_evaluator/__init__.py +41 -4
  10. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/function_builtins.cpp +1 -0
  11. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/import.cpp +50 -2
  12. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/registry.cpp +5 -2
  13. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/bytecode_vm.cpp +2 -0
  14. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/csg_generate.cpp +14 -2
  15. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/csg_resolve.cpp +3 -0
  16. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/import/svg_import.cpp +53 -2
  17. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_dxf_svg_import.cpp +102 -0
  18. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_feature_detection.cpp +2 -1
  19. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_python_bindings.py +170 -0
  20. openscad_cpp_evaluator-1.20.5/src/.DS_Store +0 -0
  21. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/.github/workflows/ci.yml +0 -0
  22. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/.github/workflows/wheels.yml +0 -0
  23. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/.gitmodules +0 -0
  24. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/LICENSE +0 -0
  26. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/README.md +0 -0
  27. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/bindings/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/bindings/ast_to_py.cpp +0 -0
  29. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/cmake/embed_font.cmake +0 -0
  30. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/examples/CMakeLists.txt +0 -0
  31. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/examples/eval_perf_benchmark.cpp +0 -0
  32. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/examples/manifold_cache_reuse.cpp +0 -0
  33. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/examples/minimal_debugger.cpp +0 -0
  34. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  35. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/.gitignore +0 -0
  36. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  37. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  38. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/README.md +0 -0
  39. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  40. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  41. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  43. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  44. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  45. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  46. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  47. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  48. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  49. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  50. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  51. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  52. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  53. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  54. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  55. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  56. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  57. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  58. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  59. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  60. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  61. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  62. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  63. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  64. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  65. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  66. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  67. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  68. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  69. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  70. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  71. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  72. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  73. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  74. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  75. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  76. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  77. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  78. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  79. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  80. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  81. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  82. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  83. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  84. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  85. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  86. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  87. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  88. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  89. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  90. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  91. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  92. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  93. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  94. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  95. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  96. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  97. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  98. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  99. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  100. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  101. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  102. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  103. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  104. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  105. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  106. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  107. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  108. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  109. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  110. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  111. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  112. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  113. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  114. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  115. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  116. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  117. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  118. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  119. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  120. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  121. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  122. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  123. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  124. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  125. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  126. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  127. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  128. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  129. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  130. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  131. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  132. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  133. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  134. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  135. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  136. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LICENSE +0 -0
  137. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  138. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  139. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  140. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  141. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/CMakeLists.txt +0 -0
  142. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/booleans.cpp +0 -0
  143. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/builtins.hpp +0 -0
  144. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/call_args.cpp +0 -0
  145. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/color.cpp +0 -0
  146. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/control.cpp +0 -0
  147. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/dxf_dim.cpp +0 -0
  148. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/extrude.cpp +0 -0
  149. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/modifiers.cpp +0 -0
  150. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/primitives_2d.cpp +0 -0
  151. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/primitives_3d.cpp +0 -0
  152. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/roof.cpp +0 -0
  153. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/segments.cpp +0 -0
  154. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/surface.cpp +0 -0
  155. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/text.cpp +0 -0
  156. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/topology.cpp +0 -0
  157. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/transforms.cpp +0 -0
  158. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/bytecode_compiler.cpp +0 -0
  159. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/colored_body.cpp +0 -0
  160. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/coverage.cpp +0 -0
  161. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/css_colors.cpp +0 -0
  162. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/debug/debug_repl.cpp +0 -0
  163. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/debug_profile.cpp +0 -0
  164. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/eval_context.cpp +0 -0
  165. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/eval_error.cpp +0 -0
  166. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/eval_use.cpp +0 -0
  167. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/export.cpp +0 -0
  168. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/expr_eval.cpp +0 -0
  169. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/format_closure.cpp +0 -0
  170. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/import/dxf_import.cpp +0 -0
  171. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/import/mesh_import.cpp +0 -0
  172. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/import/surface_load.cpp +0 -0
  173. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/manifold_cache.cpp +0 -0
  174. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/measure_geometry.cpp +0 -0
  175. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/mesh_check.cpp +0 -0
  176. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/stmt_eval.cpp +0 -0
  177. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/text/font_match.cpp +0 -0
  178. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/text/freetype_font_provider.cpp +0 -0
  179. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/text/text_metrics.cpp +0 -0
  180. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/user_calls.cpp +0 -0
  181. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/utf8.cpp +0 -0
  182. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/value.cpp +0 -0
  183. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/zip_stored.cpp +0 -0
  184. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/CMakeLists.txt +0 -0
  185. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_booleans.cpp +0 -0
  186. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_bytecode_compiler.cpp +0 -0
  187. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_children_separate.cpp +0 -0
  188. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_cli.cpp +0 -0
  189. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_control_flow.cpp +0 -0
  190. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_coverage.cpp +0 -0
  191. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_csg_tree.cpp +0 -0
  192. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_debug_hooks.cpp +0 -0
  193. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_eval_context.cpp +0 -0
  194. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_export.cpp +0 -0
  195. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_expr_eval.cpp +0 -0
  196. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_extrude_roof.cpp +0 -0
  197. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_function_builtins.cpp +0 -0
  198. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_helpers.hpp +0 -0
  199. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_import_export.cpp +0 -0
  200. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_include_cache.cpp +0 -0
  201. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_manifold_cache.cpp +0 -0
  202. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_mesh_check.cpp +0 -0
  203. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_multi_color_merge.cpp +0 -0
  204. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_primitives.cpp +0 -0
  205. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_profiling.cpp +0 -0
  206. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_render_expr.cpp +0 -0
  207. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_scope_trail.cpp +0 -0
  208. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_scoping.cpp +0 -0
  209. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_surface.cpp +0 -0
  210. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_tail_calls.cpp +0 -0
  211. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_text.cpp +0 -0
  212. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_transforms.cpp +0 -0
  213. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_unexpected_args.cpp +0 -0
  214. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_use.cpp +0 -0
  215. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_value.cpp +0 -0
  216. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_viewport_params.cpp +0 -0
  217. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_zip_stored.cpp +0 -0
  218. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tools/cli/CMakeLists.txt +0 -0
  219. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tools/cli/cli_lib.cpp +0 -0
  220. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tools/cli/cli_lib.hpp +0 -0
  221. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tools/cli/main.cpp +0 -0
  222. {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/uv.lock +0 -0
@@ -4,3 +4,5 @@ dist/
4
4
  wheelhouse/
5
5
  *.egg-info/
6
6
  __pycache__/
7
+ build-tbb/
8
+ .DS_Store
@@ -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
@@ -1609,8 +1621,46 @@ implementation of "CSG subtree → `object()`"; both engines call it — the
1609
1621
  interpreter from `evalRenderExpr`, the VM from `Op::PopBuiltinWrap`'s
1610
1622
  `Kind::Measure` branch.
1611
1623
 
1624
+ **`idToCallChain` is what a picker should actually walk.** `idToNode` names the
1625
+ node that *produced* the geometry, and for anything a library builds that is a
1626
+ node inside the library — a plain `cube(10)` maps into BOSL2's `builtins.scad`
1627
+ the moment BOSL2 is included, because BOSL2 overrides the primitives with its
1628
+ own modules. Pointing an editor at that span means pointing it at another file,
1629
+ or (if the consumer forgets to check `origin`) splicing a library's byte offsets
1630
+ into the user's buffer, which is how BelfrySCAD #450 appended a `translate()` to
1631
+ the end of a 59-character script.
1632
+
1633
+ `idToCallChain` maps each `originalID` to its **whole call chain**, innermost
1634
+ frame first. Deliberately unfiltered: a single `cuboid()` is 24 frames, most of
1635
+ them BOSL2's own `attachable`/`_attach_transform` layers, and someone debugging
1636
+ the library wants to step into exactly those. Which frames are reachable depends
1637
+ on what the front end has open, which the evaluator has no business guessing —
1638
+ so it records all of them and a consumer picks its level (BelfrySCAD defaults to
1639
+ the last frame in the running script).
1640
+
1641
+ It is stored as a **cactus stack**, not a list per node. Chains nest, so the
1642
+ distinct chains over a run form a tree: `callChains_` holds `{site, parent,
1643
+ isModule}` and a `CSGNode` holds one `uint32` into it. Memory tracks distinct
1644
+ call *paths* — tens on a real model; Dalek's 139 bodies share 13 innermost sites
1645
+ — rather than CSG nodes, and nothing allocates per node. That is what makes this
1646
+ affordable where "the full frame list a TRACE would need" (`csg_node.hpp`) was
1647
+ not.
1648
+
1649
+ `isModule` separates a module frame, which has geometry behind it, from a
1650
+ function frame like `_find_anchor`, which does not: both are worth showing, only
1651
+ the former is worth dragging.
1652
+
1653
+ A cache hit restamps to the chain reaching the geometry *now*, not the one that
1654
+ first produced it: a module called twice genuinely is two call chains.
1655
+
1656
+ No stored script path is needed to tell the user's file from a library:
1657
+ `callStack_.front()` is by construction the call made from top level.
1658
+
1659
+ Exposed to Python as `node.call_sites` (innermost-first `_CallFrame`s, each a
1660
+ `_Position` plus `is_module`), with `node.call_site` as the innermost.
1661
+
1612
1662
  `Evaluator::measuring_` is set for the whole generate. It suppresses the four
1613
- writes that exist solely to describe *drawn* geometry — `idToNode`/`idToColor` in
1663
+ writes that exist solely to describe *drawn* geometry — `idToNode`/`idToColor`/`idToCallChain` in
1614
1664
  `tagGenerated` and `tagDisplayOnly`, the `restampCachedIds` call on a cache hit,
1615
1665
  and `cacheProducer_` — because those tables are cleared once per pass, so a leak
1616
1666
  is permanent and surfaces later as wrong click-to-source. It also suppresses
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.20.5
3
+ Version: 1.22.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -196,13 +196,37 @@ struct IdSpan {
196
196
  uint32_t id;
197
197
  int start, end, line, column;
198
198
  std::string origin;
199
+ // The whole call chain behind this body, innermost frame first. Empty
200
+ // for geometry written at top level, where the span above is already
201
+ // the user's own source. Library frames included on purpose -- a BOSL2
202
+ // author stepping into `attachable` wants them; a front end picks the
203
+ // level it can actually show. See Evaluator::idToCallChain.
204
+ struct Frame {
205
+ int start, end, line, column;
206
+ std::string origin;
207
+ bool is_module;
208
+ };
209
+ std::vector<Frame> chain;
199
210
  };
200
211
 
201
212
  void collectIdSpans(const oscadeval::Evaluator& ev, std::vector<IdSpan>& out) {
202
213
  out.reserve(ev.idToNode.size());
203
214
  for (const auto& [id, node] : ev.idToNode) {
204
215
  const oscad::Position& p = node->position();
205
- out.push_back({id, p.start_offset, p.end_offset, p.line, p.column, p.origin});
216
+ IdSpan s{id, p.start_offset, p.end_offset, p.line, p.column, p.origin};
217
+ auto found = ev.idToCallChain.find(id);
218
+ uint32_t idx = found == ev.idToCallChain.end()
219
+ ? oscadeval::Evaluator::kNoCallChain : found->second;
220
+ while (idx != oscadeval::Evaluator::kNoCallChain && idx < ev.callChains_.size()) {
221
+ const auto& e = ev.callChains_[idx];
222
+ if (e.site) {
223
+ s.chain.push_back({e.site->start_offset, e.site->end_offset,
224
+ e.site->line, e.site->column, e.site->origin,
225
+ e.isModule});
226
+ }
227
+ idx = e.parent;
228
+ }
229
+ out.push_back(std::move(s));
206
230
  }
207
231
  }
208
232
 
@@ -219,8 +243,12 @@ nb::list bodiesToList(std::vector<oscadeval::ColoredBody>& bodies) {
219
243
 
220
244
  nb::dict idSpansToDict(const std::vector<IdSpan>& idSpans) {
221
245
  nb::dict d;
222
- for (const IdSpan& s : idSpans)
223
- d[nb::cast(s.id)] = nb::make_tuple(s.start, s.end, s.line, s.column, s.origin);
246
+ for (const IdSpan& s : idSpans) {
247
+ nb::list chain;
248
+ for (const IdSpan::Frame& f : s.chain)
249
+ chain.append(nb::make_tuple(f.start, f.end, f.line, f.column, f.origin, f.is_module));
250
+ d[nb::cast(s.id)] = nb::make_tuple(s.start, s.end, s.line, s.column, s.origin, chain);
251
+ }
224
252
  return d;
225
253
  }
226
254
 
@@ -51,6 +51,13 @@ struct CSGNode {
51
51
  // call sites still share a cache entry.
52
52
  const oscad::Position* warnEntry = nullptr;
53
53
 
54
+ // This node's interned call chain (Evaluator::callChains_), captured
55
+ // at RESOLVE time for the same reason warnEntry is: a picker runs long
56
+ // after the stack has unwound. kNoCallChain at top level. One uint32,
57
+ // not a frame list -- see Evaluator's call-chain section for why that
58
+ // is affordable where a per-node list was not.
59
+ uint32_t callChain = UINT32_MAX;
60
+
54
61
  // 1 + the deepest child's own treeDepth (1 for a leaf) -- set once,
55
62
  // at construction, by whichever csg_resolve.cpp site finalizes this
56
63
  // node's own `children` (buildTreeNode, evalModularCall's non-splice
@@ -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
@@ -284,9 +284,21 @@ public:
284
284
  // Give cached bodies fresh originalIDs so a second call site reusing
285
285
  // them is not confused with the first. See its definition.
286
286
  void restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::ASTNode& node,
287
- const oscad::ASTNode* producer);
287
+ const oscad::ASTNode* producer, uint32_t callChain);
288
288
 
289
289
  std::unordered_map<uint32_t, const oscad::ASTNode*> idToNode;
290
+ // originalID -> its interned call chain (innermost frame; walk
291
+ // callChains_[i].parent outwards), or kNoCallChain for geometry
292
+ // written at top level, where idToNode already is the user's node.
293
+ //
294
+ // idToNode names the node that PRODUCED the geometry, which for
295
+ // anything a library builds is a node inside that library -- including
296
+ // a plain `cube(10)` once BOSL2 is included, since BOSL2 overrides the
297
+ // primitives with its own modules. A consumer that wants to point an
298
+ // editor at a line the user can actually see walks this instead, and
299
+ // picks the level it wants: the last frame in the script for an
300
+ // ordinary pick, deeper for someone debugging the library itself.
301
+ std::unordered_map<uint32_t, uint32_t> idToCallChain;
290
302
  // Nodes whose bodies were actually generated this run (not served from
291
303
  // the ManifoldCache). Reset per run; a diagnostic, and the only honest
292
304
  // way for a test to tell a cache hit from a miss now that an inner
@@ -327,6 +339,57 @@ public:
327
339
  return callStack_.empty() ? nullptr : callStack_.front().callPosition;
328
340
  }
329
341
 
342
+ // -- Call chains -----------------------------------------------------
343
+ //
344
+ // The whole call chain behind a body, so a picker can step through it.
345
+ // Deliberately UNFILTERED: which frames are reachable depends on what
346
+ // the front end has open, which the evaluator has no business guessing.
347
+ // A BOSL2 author stepping into `attachable` -> `_attach_transform` is
348
+ // the case that settles it; a single cuboid() call is seven frames, six
349
+ // of them inside the library.
350
+ //
351
+ // Stored as a cactus stack rather than a list per node. Call chains
352
+ // nest, so the distinct chains over a run form a TREE: each entry is
353
+ // one frame plus its parent's index, and a CSGNode holds a single
354
+ // uint32 into the pool. Memory tracks distinct call PATHS (tens on a
355
+ // real model -- Dalek's 139 bodies share 13 innermost sites) rather
356
+ // than CSG nodes, and nothing allocates per node. That is what makes
357
+ // this affordable where "the full frame list a TRACE would need"
358
+ // (csg_node.hpp) was not.
359
+ static constexpr uint32_t kNoCallChain = UINT32_MAX;
360
+
361
+ struct CallChainEntry {
362
+ const oscad::Position* site = nullptr; // non-owning, AST-lifetime-bound
363
+ uint32_t parent = kNoCallChain; // next frame OUT, or kNoCallChain
364
+ bool isModule = false; // a function frame has no geometry to drag
365
+ };
366
+
367
+ // Intern callStack_ as it stands and return its index, or kNoCallChain
368
+ // at top level. Outermost-first walk, so a chain sharing a prefix with
369
+ // one already interned costs only its own tail.
370
+ uint32_t internCurrentCallChain() {
371
+ uint32_t parent = kNoCallChain;
372
+ for (const CallStackFrame& f : callStack_) {
373
+ if (!f.callPosition) continue;
374
+ const ChainKey key{parent, f.callPosition};
375
+ auto found = chainIndex_.find(key);
376
+ if (found != chainIndex_.end()) {
377
+ parent = found->second;
378
+ continue;
379
+ }
380
+ const uint32_t idx = static_cast<uint32_t>(callChains_.size());
381
+ callChains_.push_back({f.callPosition, parent,
382
+ f.kind == CallStackFrame::Kind::Module});
383
+ chainIndex_.emplace(key, idx);
384
+ parent = idx;
385
+ }
386
+ return parent; // the innermost frame, or kNoCallChain
387
+ }
388
+
389
+ // The interned pool. Public for the same reason idToNode is: a binding
390
+ // walks it after evaluate() to flatten each id's chain.
391
+ std::vector<CallChainEntry> callChains_;
392
+
330
393
  // Set by generateTreeImpl() to the CSGNode currently being generated,
331
394
  // so warn() can name the user's own call site during a phase where
332
395
  // callStack_ is necessarily empty. Public for the same reason
@@ -335,6 +398,9 @@ public:
335
398
  // than anything reentrancy-aware, since generateTreeImpl recurses
336
399
  // depth-first on one thread.
337
400
  const oscad::Position* generateWarnEntry = nullptr;
401
+ // The same republish for CSGNode::callChain, read by tagGenerated to
402
+ // fill idToCallChain.
403
+ uint32_t generateCallChain = kNoCallChain;
338
404
 
339
405
  // Set while generating anything beneath a hull(). An open mesh there is
340
406
  // not a mistake to report: a convex hull needs only points, so BOSL2's
@@ -1790,6 +1856,22 @@ private:
1790
1856
  // eval_error.hpp).
1791
1857
  std::vector<CallStackFrame> callStack_;
1792
1858
 
1859
+ // (parent chain index, frame position) -> chain index, the memo that
1860
+ // makes internCurrentCallChain() share prefixes. Cleared with the pool
1861
+ // wherever idToNode is.
1862
+ struct ChainKey {
1863
+ uint32_t parent;
1864
+ const oscad::Position* site;
1865
+ bool operator==(const ChainKey& o) const { return parent == o.parent && site == o.site; }
1866
+ };
1867
+ struct ChainKeyHash {
1868
+ std::size_t operator()(const ChainKey& k) const {
1869
+ return std::hash<uint32_t>{}(k.parent) ^
1870
+ (std::hash<const void*>{}(k.site) << 1);
1871
+ }
1872
+ };
1873
+ std::unordered_map<ChainKey, uint32_t, ChainKeyHash> chainIndex_;
1874
+
1793
1875
  // Count of callStack_ entries that ACTUALLY cost native C++ stack --
1794
1876
  // i.e. pushed with skipDepthGuard=false (enterUserCall's own
1795
1877
  // interpreted-call sites: evalUserFunctionCore, evalUserModule).
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.20.5"
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"
@@ -47,11 +47,43 @@ class _Position:
47
47
 
48
48
 
49
49
  class _DeclNode:
50
- __slots__ = ("name", "position")
50
+ __slots__ = ("name", "position", "call_sites")
51
51
 
52
- def __init__(self, name, position):
52
+ def __init__(self, name, position, call_sites=()):
53
53
  self.name = name
54
54
  self.position = position
55
+ #: For a geometry node: the whole call chain behind it, INNERMOST
56
+ #: frame first, each a `_CallFrame`. Empty for geometry written at
57
+ #: top level, where `position` is already the user's own source.
58
+ #:
59
+ #: `position` names the node that PRODUCED the geometry, which for
60
+ #: anything a library builds is inside that library -- a plain
61
+ #: `cube(10)` lands in BOSL2's builtins.scad once BOSL2 is included.
62
+ #: A caller that wants a line to show the user walks this and picks
63
+ #: the level it can display: the last frame in the running script
64
+ #: for an ordinary pick, a deeper one for someone reading the
65
+ #: library itself. Library frames are included deliberately.
66
+ self.call_sites = tuple(call_sites)
67
+
68
+ @property
69
+ def call_site(self):
70
+ """The innermost frame, or None. Convenience for a caller that does
71
+ not care about the chain."""
72
+ return self.call_sites[0] if self.call_sites else None
73
+
74
+
75
+ class _CallFrame(_Position):
76
+ """One call in a chain: a position, plus whether it is a MODULE call.
77
+
78
+ A function frame (`_find_anchor`, an `assert`) has no geometry of its
79
+ own to drag, so a gizmo should decline it even though it is worth
80
+ showing.
81
+ """
82
+ __slots__ = ("is_module",)
83
+
84
+ def __init__(self, line, column, origin, start_offset, end_offset, is_module):
85
+ super().__init__(line, column, origin, start_offset, end_offset)
86
+ self.is_module = is_module
55
87
 
56
88
 
57
89
  class OscObject:
@@ -635,8 +667,13 @@ class Evaluator:
635
667
  # originalID -> a node with `.position` (start/end offsets) for WYSIWYG
636
668
  # picking and gizmo write-back.
637
669
  id_to_node = {
638
- oid: _DeclNode(None, _Position(line, column, origin, start, end))
639
- for oid, (start, end, line, column, origin) in id_spans.items()
670
+ oid: _DeclNode(
671
+ None,
672
+ _Position(line, column, origin, start, end),
673
+ [_CallFrame(cl, cc, co, cs, ce, mod)
674
+ for (cs, ce, cl, cc, co, mod) in chain],
675
+ )
676
+ for oid, (start, end, line, column, origin, chain) in id_spans.items()
640
677
  }
641
678
  return bodies, id_to_node
642
679
 
@@ -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"}},
@@ -1400,6 +1400,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1400
1400
  treeNode->node = site.node;
1401
1401
  treeNode->isBuiltin = true;
1402
1402
  treeNode->warnEntry = ev.currentWarnEntry();
1403
+ treeNode->callChain = ev.internCurrentCallChain();
1403
1404
  treeNode->children = std::move(children);
1404
1405
  treeNode->params = std::move(pending.params);
1405
1406
  treeNode->uncacheable = uncacheable;
@@ -1503,6 +1504,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1503
1504
  treeNode->node = site.node;
1504
1505
  treeNode->isBuiltin = true;
1505
1506
  treeNode->warnEntry = ev.currentWarnEntry();
1507
+ treeNode->callChain = ev.internCurrentCallChain();
1506
1508
  treeNode->children = std::move(children);
1507
1509
  treeNode->params = std::move(params);
1508
1510
  treeNode->uncacheable = uncacheable;
@@ -68,7 +68,8 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
68
68
  if (node.node && !measuring_) {
69
69
  auto producer = cacheProducer_.find(*key);
70
70
  restampCachedIds(node.bodies, *node.node,
71
- producer == cacheProducer_.end() ? nullptr : producer->second);
71
+ producer == cacheProducer_.end() ? nullptr : producer->second,
72
+ node.callChain);
72
73
  }
73
74
  } else {
74
75
  // Recurse into children first (bottom-up) -- populates each
@@ -95,9 +96,12 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
95
96
  // can name that line. Restored after (rather than left set)
96
97
  // so a sibling generated at top level doesn't inherit it.
97
98
  const oscad::Position* savedWarnEntry = generateWarnEntry;
99
+ const uint32_t savedCallChain = generateCallChain;
98
100
  generateWarnEntry = node.warnEntry;
101
+ generateCallChain = node.callChain;
99
102
  node.bodies = it->second(*this, node.params, node.children, *node.node);
100
103
  generateWarnEntry = savedWarnEntry;
104
+ generateCallChain = savedCallChain;
101
105
  } else {
102
106
  // No registered GenerateFn for this kind (a builtin with
103
107
  // display-only semantics like render(), or a non-builtin
@@ -331,7 +335,7 @@ std::shared_ptr<const std::vector<ColoredBody>> remapParts(const std::vector<Col
331
335
  } // namespace
332
336
 
333
337
  void Evaluator::restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::ASTNode& node,
334
- const oscad::ASTNode* producer) {
338
+ const oscad::ASTNode* producer, uint32_t callChain) {
335
339
  // A cache hit hands back the geometry AND the originalIDs of whichever
336
340
  // call site first produced it. Those IDs are provenance -- "which node
337
341
  // made this" -- not content, so two identical shapes at two call sites
@@ -374,6 +378,12 @@ void Evaluator::restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::
374
378
  auto old = idToNode.find(id);
375
379
  idToNode[fresh] =
376
380
  (old != idToNode.end() && old->second != producer) ? old->second : &node;
381
+ // Unlike idToNode, this is the call site that reached the
382
+ // geometry NOW: whichever node the cached copy is being
383
+ // reused at is the line the user would be shown, whatever
384
+ // produced the original. A module called twice really is
385
+ // two different call sites.
386
+ idToCallChain[fresh] = callChain;
377
387
  // The colour has to be re-recorded too, or a later merge
378
388
  // cannot tell this body's runs apart from uncoloured ones
379
389
  // (attachTriColors looks runs up here). Across renders
@@ -453,6 +463,7 @@ ColoredBody Evaluator::tagGenerated(manifold::Manifold body, const oscad::ASTNod
453
463
  for (uint32_t originalId : mesh.runOriginalID) {
454
464
  idToNode[originalId] = &node;
455
465
  idToColor[originalId] = color;
466
+ idToCallChain[originalId] = generateCallChain;
456
467
  }
457
468
  }
458
469
  ColoredBody cb;
@@ -474,6 +485,7 @@ ColoredBody Evaluator::tagDisplayOnly(manifold::MeshGL mesh, const oscad::ASTNod
474
485
  if (!measuring_) {
475
486
  idToNode[originalId] = &node;
476
487
  idToColor[originalId] = color;
488
+ idToCallChain[originalId] = generateCallChain;
477
489
  }
478
490
 
479
491
  ColoredBody cb;
@@ -68,6 +68,7 @@ void Evaluator::buildTreeNode(const std::string& kind, const oscad::ASTNode& nod
68
68
  treeNode->node = &node;
69
69
  treeNode->isBuiltin = true;
70
70
  treeNode->warnEntry = currentWarnEntry();
71
+ treeNode->callChain = internCurrentCallChain();
71
72
  treeNode->children = std::move(children);
72
73
  treeNode->params = std::move(params);
73
74
  treeNode->uncacheable = uncacheable;
@@ -139,6 +140,7 @@ void Evaluator::evalModularCall(const oscad::ModularCall& node, EvalContext& ctx
139
140
  treeNode->node = &node;
140
141
  treeNode->isBuiltin = true;
141
142
  treeNode->warnEntry = currentWarnEntry();
143
+ treeNode->callChain = internCurrentCallChain();
142
144
  treeNode->uncacheable = uncacheable;
143
145
  treeNode->children = std::move(children);
144
146
  treeNode->params = std::move(params);
@@ -164,6 +166,7 @@ void Evaluator::spliceModuleChildren(std::vector<std::unique_ptr<CSGNode>> child
164
166
  unionNode->node = &callNode;
165
167
  unionNode->isBuiltin = false;
166
168
  unionNode->warnEntry = currentWarnEntry();
169
+ unionNode->callChain = internCurrentCallChain();
167
170
  unionNode->uncacheable = std::any_of(children.begin(), children.end(), [](const auto& c) { return c->uncacheable; });
168
171
  unionNode->children = std::move(children);
169
172
  setTreeDepthOrThrow(*unionNode, callNode);
@@ -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