openscad-cpp-evaluator 1.20.3__tar.gz → 1.21.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.3 → openscad_cpp_evaluator-1.21.0}/.gitignore +2 -0
  2. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/CLAUDE.md +39 -1
  3. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/PKG-INFO +1 -1
  4. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/bindings/module.cpp +31 -3
  5. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/csg_node.hpp +7 -0
  6. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/evaluator.hpp +83 -1
  7. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/pyproject.toml +1 -1
  8. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/python/openscad_cpp_evaluator/__init__.py +41 -4
  9. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/bytecode_vm.cpp +2 -0
  10. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/csg_generate.cpp +14 -2
  11. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/csg_resolve.cpp +3 -0
  12. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/export.cpp +33 -18
  13. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/mesh_check.cpp +81 -26
  14. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_export.cpp +64 -0
  15. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_mesh_check.cpp +20 -0
  16. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_python_bindings.py +116 -0
  17. openscad_cpp_evaluator-1.20.3/src/.DS_Store +0 -0
  18. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/.github/workflows/ci.yml +0 -0
  19. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/.github/workflows/wheels.yml +0 -0
  20. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/.gitmodules +0 -0
  21. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/LICENSE +0 -0
  23. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/README.md +0 -0
  24. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/bindings/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/bindings/ast_to_py.cpp +0 -0
  26. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/cmake/embed_font.cmake +0 -0
  27. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/examples/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/examples/eval_perf_benchmark.cpp +0 -0
  29. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/examples/manifold_cache_reuse.cpp +0 -0
  30. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/examples/minimal_debugger.cpp +0 -0
  31. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  32. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/.gitignore +0 -0
  33. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  34. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  35. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/README.md +0 -0
  36. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  37. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  38. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  39. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  40. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  41. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  42. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  43. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  44. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  45. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  46. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  47. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  48. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  49. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  50. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  51. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  52. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  53. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  54. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  55. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  56. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  57. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  58. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  59. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  60. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  61. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  62. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  63. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  64. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  65. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  66. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  67. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  68. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  69. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  70. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  71. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  72. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  73. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  74. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  75. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  76. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  77. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  78. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  79. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  80. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  81. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  82. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  83. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  84. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  85. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  86. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  87. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  88. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  89. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  90. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  91. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  92. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  93. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  94. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  95. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  96. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  97. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  98. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  99. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  100. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  101. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  102. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  103. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  104. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  105. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  106. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  107. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  108. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  109. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  110. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  111. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  112. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  113. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  114. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  115. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  116. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  117. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  118. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  119. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  120. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  121. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  122. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  123. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  124. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  125. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  126. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  127. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  128. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  129. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  130. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  131. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  132. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  133. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  134. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/resources/fonts/LICENSE +0 -0
  135. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  136. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  137. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  138. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  139. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/CMakeLists.txt +0 -0
  140. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/booleans.cpp +0 -0
  141. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/builtins.hpp +0 -0
  142. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/call_args.cpp +0 -0
  143. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/color.cpp +0 -0
  144. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/control.cpp +0 -0
  145. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/dxf_dim.cpp +0 -0
  146. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/extrude.cpp +0 -0
  147. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/function_builtins.cpp +0 -0
  148. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/import.cpp +0 -0
  149. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/modifiers.cpp +0 -0
  150. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/primitives_2d.cpp +0 -0
  151. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/primitives_3d.cpp +0 -0
  152. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/registry.cpp +0 -0
  153. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/roof.cpp +0 -0
  154. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/segments.cpp +0 -0
  155. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/surface.cpp +0 -0
  156. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/text.cpp +0 -0
  157. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/topology.cpp +0 -0
  158. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/builtins/transforms.cpp +0 -0
  159. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/bytecode_compiler.cpp +0 -0
  160. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/colored_body.cpp +0 -0
  161. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/coverage.cpp +0 -0
  162. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/css_colors.cpp +0 -0
  163. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/debug/debug_repl.cpp +0 -0
  164. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/debug_profile.cpp +0 -0
  165. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/eval_context.cpp +0 -0
  166. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/eval_error.cpp +0 -0
  167. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/eval_use.cpp +0 -0
  168. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/expr_eval.cpp +0 -0
  169. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/format_closure.cpp +0 -0
  170. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/import/dxf_import.cpp +0 -0
  171. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/import/mesh_import.cpp +0 -0
  172. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/import/surface_load.cpp +0 -0
  173. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/import/svg_import.cpp +0 -0
  174. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/manifold_cache.cpp +0 -0
  175. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/measure_geometry.cpp +0 -0
  176. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/stmt_eval.cpp +0 -0
  177. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/text/font_match.cpp +0 -0
  178. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/text/freetype_font_provider.cpp +0 -0
  179. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/text/text_metrics.cpp +0 -0
  180. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/user_calls.cpp +0 -0
  181. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/utf8.cpp +0 -0
  182. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/value.cpp +0 -0
  183. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/src/zip_stored.cpp +0 -0
  184. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/CMakeLists.txt +0 -0
  185. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_booleans.cpp +0 -0
  186. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_bytecode_compiler.cpp +0 -0
  187. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_children_separate.cpp +0 -0
  188. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_cli.cpp +0 -0
  189. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_control_flow.cpp +0 -0
  190. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_coverage.cpp +0 -0
  191. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_csg_tree.cpp +0 -0
  192. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_debug_hooks.cpp +0 -0
  193. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_dxf_svg_import.cpp +0 -0
  194. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_eval_context.cpp +0 -0
  195. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_expr_eval.cpp +0 -0
  196. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_extrude_roof.cpp +0 -0
  197. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_feature_detection.cpp +0 -0
  198. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_function_builtins.cpp +0 -0
  199. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_helpers.hpp +0 -0
  200. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_import_export.cpp +0 -0
  201. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_include_cache.cpp +0 -0
  202. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_manifold_cache.cpp +0 -0
  203. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_multi_color_merge.cpp +0 -0
  204. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_primitives.cpp +0 -0
  205. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_profiling.cpp +0 -0
  206. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_render_expr.cpp +0 -0
  207. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_scope_trail.cpp +0 -0
  208. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_scoping.cpp +0 -0
  209. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_surface.cpp +0 -0
  210. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_tail_calls.cpp +0 -0
  211. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_text.cpp +0 -0
  212. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_transforms.cpp +0 -0
  213. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_unexpected_args.cpp +0 -0
  214. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_use.cpp +0 -0
  215. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_value.cpp +0 -0
  216. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_viewport_params.cpp +0 -0
  217. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tests/test_zip_stored.cpp +0 -0
  218. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tools/cli/CMakeLists.txt +0 -0
  219. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tools/cli/cli_lib.cpp +0 -0
  220. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tools/cli/cli_lib.hpp +0 -0
  221. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.0}/tools/cli/main.cpp +0 -0
  222. {openscad_cpp_evaluator-1.20.3 → openscad_cpp_evaluator-1.21.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
@@ -1609,8 +1609,46 @@ implementation of "CSG subtree → `object()`"; both engines call it — the
1609
1609
  interpreter from `evalRenderExpr`, the VM from `Op::PopBuiltinWrap`'s
1610
1610
  `Kind::Measure` branch.
1611
1611
 
1612
+ **`idToCallChain` is what a picker should actually walk.** `idToNode` names the
1613
+ node that *produced* the geometry, and for anything a library builds that is a
1614
+ node inside the library — a plain `cube(10)` maps into BOSL2's `builtins.scad`
1615
+ the moment BOSL2 is included, because BOSL2 overrides the primitives with its
1616
+ own modules. Pointing an editor at that span means pointing it at another file,
1617
+ or (if the consumer forgets to check `origin`) splicing a library's byte offsets
1618
+ into the user's buffer, which is how BelfrySCAD #450 appended a `translate()` to
1619
+ the end of a 59-character script.
1620
+
1621
+ `idToCallChain` maps each `originalID` to its **whole call chain**, innermost
1622
+ frame first. Deliberately unfiltered: a single `cuboid()` is 24 frames, most of
1623
+ them BOSL2's own `attachable`/`_attach_transform` layers, and someone debugging
1624
+ the library wants to step into exactly those. Which frames are reachable depends
1625
+ on what the front end has open, which the evaluator has no business guessing —
1626
+ so it records all of them and a consumer picks its level (BelfrySCAD defaults to
1627
+ the last frame in the running script).
1628
+
1629
+ It is stored as a **cactus stack**, not a list per node. Chains nest, so the
1630
+ distinct chains over a run form a tree: `callChains_` holds `{site, parent,
1631
+ isModule}` and a `CSGNode` holds one `uint32` into it. Memory tracks distinct
1632
+ call *paths* — tens on a real model; Dalek's 139 bodies share 13 innermost sites
1633
+ — rather than CSG nodes, and nothing allocates per node. That is what makes this
1634
+ affordable where "the full frame list a TRACE would need" (`csg_node.hpp`) was
1635
+ not.
1636
+
1637
+ `isModule` separates a module frame, which has geometry behind it, from a
1638
+ function frame like `_find_anchor`, which does not: both are worth showing, only
1639
+ the former is worth dragging.
1640
+
1641
+ A cache hit restamps to the chain reaching the geometry *now*, not the one that
1642
+ first produced it: a module called twice genuinely is two call chains.
1643
+
1644
+ No stored script path is needed to tell the user's file from a library:
1645
+ `callStack_.front()` is by construction the call made from top level.
1646
+
1647
+ Exposed to Python as `node.call_sites` (innermost-first `_CallFrame`s, each a
1648
+ `_Position` plus `is_module`), with `node.call_site` as the innermost.
1649
+
1612
1650
  `Evaluator::measuring_` is set for the whole generate. It suppresses the four
1613
- writes that exist solely to describe *drawn* geometry — `idToNode`/`idToColor` in
1651
+ writes that exist solely to describe *drawn* geometry — `idToNode`/`idToColor`/`idToCallChain` in
1614
1652
  `tagGenerated` and `tagDisplayOnly`, the `restampCachedIds` call on a cache hit,
1615
1653
  and `cacheProducer_` — because those tables are cleared once per pass, so a leak
1616
1654
  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.3
3
+ Version: 1.21.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
@@ -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.3"
7
+ version = "1.21.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
 
@@ -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);
@@ -362,9 +362,7 @@ void meshToArrays(const manifold::MeshGL& mesh, std::vector<float>& verts, std::
362
362
  tris = mesh.triVerts;
363
363
  }
364
364
 
365
- bool boxesOverlap(const manifold::Manifold& a, const manifold::Manifold& b) {
366
- const manifold::Box ba = a.BoundingBox();
367
- const manifold::Box bb = b.BoundingBox();
365
+ bool boxesOverlap(const manifold::Box& ba, const manifold::Box& bb) {
368
366
  return !(ba.max.x < bb.min.x || bb.max.x < ba.min.x || ba.max.y < bb.min.y || bb.max.y < ba.min.y ||
369
367
  ba.max.z < bb.min.z || bb.max.z < ba.min.z);
370
368
  }
@@ -512,25 +510,42 @@ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& b
512
510
  c.key = keys[0];
513
511
  claimedGroups.push_back(std::move(c));
514
512
  } else {
515
- // Reverse order + subtract-what-is-already-claimed is what makes
516
- // the LATER body win: by the time an earlier one is reached,
517
- // everything after it has already taken its volume.
518
- std::optional<manifold::Manifold> claimed;
513
+ // Reverse order is what makes the LATER body win: by the time
514
+ // an earlier one is reached, everything after it has already
515
+ // taken its volume.
516
+ //
517
+ // Each body is cut by the later ones that actually REACH it, not
518
+ // by a running union of all of them. The running union was
519
+ // quadratic -- it grows to span the whole model, so every one of
520
+ // n subtractions faced O(n) geometry however little the parts
521
+ // really touched, and a 100-body 3MF spent 6.6s on it (#177).
522
+ //
523
+ // The result is identical: a later body whose bounding box is
524
+ // disjoint from this one cannot intersect it, and subtracting a
525
+ // disjoint solid removes no volume. Bounding boxes settle that in
526
+ // nanoseconds, so what is left is proportional to how much the
527
+ // model overlaps rather than to how many parts it has.
528
+ //
529
+ // Skipping the subtraction entirely when nothing overlaps is not
530
+ // just a shortcut: `A - disjoint B` returns A's volume but
531
+ // REORDERS its triangle list, which throws away any per-triangle
532
+ // colours A carried. Most models are mostly disjoint parts, so
533
+ // without this a two-tone body lost its colours the moment any
534
+ // other differently-coloured body existed.
535
+ std::vector<manifold::Box> boxes;
536
+ boxes.reserve(solids.size());
537
+ for (const Solid& s : solids) boxes.push_back(s.man.BoundingBox());
538
+
519
539
  std::vector<Claimed> owned;
540
+ std::vector<manifold::Manifold> blockers;
520
541
  for (size_t n = solids.size(); n-- > 0;) {
521
542
  Solid& s = solids[n];
522
- manifold::Manifold piece = s.man;
523
- if (claimed) {
524
- // Skipping the subtraction when the bounding boxes
525
- // cannot overlap is not just a shortcut: `A - disjoint
526
- // B` returns A's volume but REORDERS its triangle list,
527
- // which throws away any per-triangle colours A carried.
528
- // Most models are mostly disjoint parts, so without this
529
- // a two-tone body lost its colours the moment any other
530
- // differently-coloured body existed.
531
- if (boxesOverlap(s.man, *claimed)) piece = s.man - *claimed;
543
+ blockers.clear();
544
+ for (size_t m = n + 1; m < solids.size(); ++m) {
545
+ if (boxesOverlap(boxes[n], boxes[m])) blockers.push_back(solids[m].man);
532
546
  }
533
- claimed = claimed ? (*claimed + s.man) : s.man;
547
+ manifold::Manifold piece = s.man;
548
+ if (!blockers.empty()) piece = s.man - addAll(blockers);
534
549
  if (piece.IsEmpty()) continue;
535
550
  Claimed c;
536
551
  c.man = std::move(piece);
@@ -79,20 +79,56 @@ size_t countUnwelded(const M& m) {
79
79
  // The faces around `v`, as (prev, next) pairs on the opposite edge. The link
80
80
  // is one cycle when following those pairs from any starting corner visits
81
81
  // all of them; more than one walk means the surface pinches at v.
82
+ //
83
+ // `vertTris` is CSR: the triangles around vertex v are
84
+ // vertTriList[vertTriStart[v] .. vertTriStart[v+1]).
85
+ //
86
+ // Every buffer here is hoisted out of the loop and cleared per vertex. The
87
+ // straightforward spelling -- an unordered_map of vectors for the link and a
88
+ // std::set for the walk, both built fresh per vertex -- allocated on the
89
+ // order of a dozen blocks for each of a mesh's vertices, millions of them on
90
+ // a 200K-triangle model, to answer a question about roughly six neighbours.
91
+ // Same reason the edge and face counts below collect flat and sort; see
92
+ // their comment.
82
93
  template <typename M>
83
94
  size_t countPinched(const M& m,
84
- const std::vector<std::vector<size_t>>& vertTris) {
95
+ const std::vector<size_t>& vertTriStart,
96
+ const std::vector<size_t>& vertTriList) {
85
97
  size_t pinched = 0;
86
- for (Vert v = 0; v < vertTris.size(); ++v) {
87
- const auto& tris = vertTris[v];
88
- if (tris.size() < 2) continue;
89
- // next[a] = b for the corner opposite v in each face, undirected so
90
- // a reversed neighbour still links up -- winding is checked
91
- // separately and should not show up as a pinch too.
92
- std::unordered_map<Vert, std::vector<Vert>> link;
93
- for (size_t t : tris) {
98
+ const size_t nVerts = vertTriStart.empty() ? 0 : vertTriStart.size() - 1;
99
+
100
+ std::vector<Vert> nodes; // the distinct neighbours of v
101
+ std::vector<size_t> parent; // union-find over indices into `nodes`
102
+ std::vector<std::pair<uint32_t, uint32_t>> edges;
103
+
104
+ // Linear search, not a map: a vertex has a handful of neighbours, and at
105
+ // that size a scan beats hashing outright -- no allocation, no hashing,
106
+ // and the whole thing stays in cache.
107
+ const auto indexOf = [&](Vert w) -> uint32_t {
108
+ for (size_t i = 0; i < nodes.size(); ++i) {
109
+ if (nodes[i] == w) return static_cast<uint32_t>(i);
110
+ }
111
+ nodes.push_back(w);
112
+ parent.push_back(parent.size());
113
+ return static_cast<uint32_t>(nodes.size() - 1);
114
+ };
115
+ const auto find = [&](size_t i) {
116
+ while (parent[i] != i) {
117
+ parent[i] = parent[parent[i]]; // path halving
118
+ i = parent[i];
119
+ }
120
+ return i;
121
+ };
122
+
123
+ for (Vert v = 0; v < nVerts; ++v) {
124
+ const size_t begin = vertTriStart[v], end = vertTriStart[v + 1];
125
+ if (end - begin < 2) continue;
126
+ nodes.clear();
127
+ parent.clear();
128
+ edges.clear();
129
+ for (size_t k = begin; k < end; ++k) {
94
130
  Vert w[3];
95
- triVerts(m, t, w);
131
+ triVerts(m, vertTriList[k], w);
96
132
  Vert a = 0, b = 0;
97
133
  int found = 0;
98
134
  for (int i = 0; i < 3; ++i) {
@@ -100,21 +136,20 @@ size_t countPinched(const M& m,
100
136
  (found++ == 0 ? a : b) = w[i];
101
137
  }
102
138
  if (found < 2) continue; // degenerate; counted elsewhere
103
- link[a].push_back(b);
104
- link[b].push_back(a);
139
+ edges.emplace_back(indexOf(a), indexOf(b));
105
140
  }
106
- if (link.empty()) continue;
107
- std::set<Vert> visited;
108
- std::vector<Vert> stack{link.begin()->first};
109
- while (!stack.empty()) {
110
- Vert cur = stack.back();
111
- stack.pop_back();
112
- if (!visited.insert(cur).second) continue;
113
- for (Vert nxt : link[cur]) {
114
- if (!visited.count(nxt)) stack.push_back(nxt);
115
- }
141
+ if (nodes.empty()) continue;
142
+ // Undirected, so a reversed neighbour still links up -- winding is
143
+ // checked separately and should not show up as a pinch too.
144
+ for (const auto& e : edges) {
145
+ const size_t ra = find(e.first), rb = find(e.second);
146
+ if (ra != rb) parent[ra] = rb;
147
+ }
148
+ size_t components = 0;
149
+ for (size_t i = 0; i < parent.size(); ++i) {
150
+ if (find(i) == i) ++components;
116
151
  }
117
- if (visited.size() < link.size()) ++pinched;
152
+ if (components > 1) ++pinched;
118
153
  }
119
154
  return pinched;
120
155
  }
@@ -158,10 +193,16 @@ MeshDiagnosis checkMesh(const M& mesh) {
158
193
  // index below; see its comment for the measurement.
159
194
  std::vector<std::pair<uint64_t, bool>> uses; // {undirected edge, traversed a->b}
160
195
  uses.reserve(tris * 3);
161
- std::vector<std::vector<size_t>> vertTris(nVerts);
162
196
  std::vector<std::array<Vert, 3>> seenFaces; // sorted-corner triples, deduplicated below
163
197
  seenFaces.reserve(tris);
164
198
 
199
+ // The triangles around each vertex, CSR rather than a vector per vertex:
200
+ // one inner vector per vertex is an allocation per vertex, and they are
201
+ // only ever read back as a flat run. Counted here, filled after the scan.
202
+ std::vector<size_t> vertTriStart(nVerts + 1, 0);
203
+ std::vector<size_t> vertTriList;
204
+ vertTriList.reserve(tris * 3);
205
+
165
206
  for (size_t t = 0; t < tris; ++t) {
166
207
  if (degenerate(mesh, t)) ++d.degenerateFaces;
167
208
  // Counted, but NOT removed from the topology. A zero-area triangle
@@ -181,12 +222,26 @@ MeshDiagnosis checkMesh(const M& mesh) {
181
222
  seenFaces.push_back(sorted);
182
223
 
183
224
  for (int i = 0; i < 3; ++i) {
184
- if (v[i] < nVerts) vertTris[v[i]].push_back(t);
225
+ if (v[i] < nVerts) ++vertTriStart[v[i] + 1];
185
226
  const Vert a = v[i], b = v[(i + 1) % 3];
186
227
  uses.emplace_back(edgeKey(undirected(a, b)), a < b);
187
228
  }
188
229
  }
189
230
 
231
+ for (size_t i = 0; i < nVerts; ++i) vertTriStart[i + 1] += vertTriStart[i];
232
+ vertTriList.resize(vertTriStart[nVerts]);
233
+ {
234
+ std::vector<size_t> fill(vertTriStart.begin(), vertTriStart.end() - 1);
235
+ for (size_t t = 0; t < tris; ++t) {
236
+ if (repeatsAVertex(mesh, t)) continue; // skipped above too
237
+ Vert v[3];
238
+ triVerts(mesh, t, v);
239
+ for (int i = 0; i < 3; ++i) {
240
+ if (v[i] < nVerts) vertTriList[fill[v[i]]++] = t;
241
+ }
242
+ }
243
+ }
244
+
190
245
  // A face seen N times is N-1 duplicates, which is what insert().second
191
246
  // counted one at a time.
192
247
  std::sort(seenFaces.begin(), seenFaces.end());
@@ -214,7 +269,7 @@ MeshDiagnosis checkMesh(const M& mesh) {
214
269
  i = j;
215
270
  }
216
271
 
217
- d.pinchedVertices = countPinched(mesh, vertTris);
272
+ d.pinchedVertices = countPinched(mesh, vertTriStart, vertTriList);
218
273
  d.unweldedVertices = countUnwelded(mesh);
219
274
  return d;
220
275
  }