openscad-cpp-evaluator 1.37.1__tar.gz → 1.38.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 (232) hide show
  1. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/CLAUDE.md +54 -0
  2. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/CMakeLists.txt +21 -2
  3. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/PKG-INFO +1 -1
  4. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +1 -0
  5. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +14 -0
  6. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +1 -0
  7. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/expression.cpp +13 -0
  8. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +5 -0
  9. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +1 -0
  10. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/grammar/lexer.l +3 -0
  11. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/grammar/parser.y +12 -1
  12. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +6 -0
  13. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/pretty_print.cpp +3 -0
  14. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +11 -0
  15. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +1 -0
  16. openscad_cpp_evaluator-1.38.0/external/openscad_cpp_parser/tests/test_profile_time.cpp +86 -0
  17. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/csg_node.hpp +7 -0
  18. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/evaluator.hpp +17 -0
  19. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/pyproject.toml +1 -1
  20. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/CMakeLists.txt +1 -0
  21. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/builtins.hpp +6 -0
  22. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/function_builtins.cpp +1 -0
  23. openscad_cpp_evaluator-1.38.0/src/builtins/profile_time.cpp +63 -0
  24. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/registry.cpp +2 -0
  25. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/coverage.cpp +6 -0
  26. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/csg_generate.cpp +11 -0
  27. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/csg_resolve.cpp +19 -0
  28. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/expr_eval.cpp +15 -0
  29. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/format_closure.cpp +4 -0
  30. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/CMakeLists.txt +1 -0
  31. openscad_cpp_evaluator-1.38.0/tests/test_profile_time.cpp +139 -0
  32. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/uv.lock +1 -1
  33. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/.github/workflows/ci.yml +0 -0
  34. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/.github/workflows/wheels.yml +0 -0
  35. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/.gitignore +0 -0
  36. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/.gitmodules +0 -0
  37. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/LICENSE +0 -0
  38. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/README.md +0 -0
  39. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/bindings/CMakeLists.txt +0 -0
  40. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/bindings/ast_to_py.cpp +0 -0
  41. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/bindings/module.cpp +0 -0
  42. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/cmake/embed_font.cmake +0 -0
  43. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/cmake/patch_manifold.cmake +0 -0
  44. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/examples/CMakeLists.txt +0 -0
  45. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/examples/eval_perf_benchmark.cpp +0 -0
  46. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/examples/manifold_cache_reuse.cpp +0 -0
  47. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/examples/minimal_debugger.cpp +0 -0
  48. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  49. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/.gitignore +0 -0
  50. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  51. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  52. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/README.md +0 -0
  53. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  54. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  55. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  56. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  57. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  58. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  59. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  60. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  61. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  62. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  63. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  64. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  65. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  66. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  67. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  68. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  69. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  70. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  71. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  72. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  73. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  74. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  75. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  76. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  77. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  78. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  79. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  80. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  81. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  82. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  83. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  84. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  85. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  86. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  87. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  88. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  89. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  90. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  91. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  92. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  93. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  94. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  95. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  96. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  97. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  98. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  99. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  100. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  101. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  102. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  103. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  104. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  105. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  106. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  107. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  108. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  109. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  110. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  111. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  112. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  113. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  114. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  115. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  116. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  117. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  118. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  119. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  120. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  121. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  122. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  123. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  124. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  125. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  126. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  127. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  128. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  129. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  130. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  131. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  132. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  133. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  134. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  135. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  136. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  137. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  138. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  139. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  140. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  141. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  142. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/resources/fonts/LICENSE +0 -0
  143. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  144. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  145. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  146. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  147. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/booleans.cpp +0 -0
  148. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/call_args.cpp +0 -0
  149. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/color.cpp +0 -0
  150. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/control.cpp +0 -0
  151. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/dxf_dim.cpp +0 -0
  152. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/extrude.cpp +0 -0
  153. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/import.cpp +0 -0
  154. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/modifiers.cpp +0 -0
  155. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/primitives_2d.cpp +0 -0
  156. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/primitives_3d.cpp +0 -0
  157. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/roof.cpp +0 -0
  158. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/segments.cpp +0 -0
  159. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/surface.cpp +0 -0
  160. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/text.cpp +0 -0
  161. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/topology.cpp +0 -0
  162. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/builtins/transforms.cpp +0 -0
  163. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/bytecode_compiler.cpp +0 -0
  164. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/bytecode_vm.cpp +0 -0
  165. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/colored_body.cpp +0 -0
  166. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/css_colors.cpp +0 -0
  167. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/debug/debug_repl.cpp +0 -0
  168. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/debug_profile.cpp +0 -0
  169. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/eval_context.cpp +0 -0
  170. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/eval_error.cpp +0 -0
  171. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/eval_use.cpp +0 -0
  172. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/export.cpp +0 -0
  173. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/import/dxf_import.cpp +0 -0
  174. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/import/mesh_import.cpp +0 -0
  175. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/import/scene_import.cpp +0 -0
  176. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/import/surface_load.cpp +0 -0
  177. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/import/svg_import.cpp +0 -0
  178. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/manifold_cache.cpp +0 -0
  179. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/measure_geometry.cpp +0 -0
  180. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/mesh_check.cpp +0 -0
  181. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/stmt_eval.cpp +0 -0
  182. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/text/font_match.cpp +0 -0
  183. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/text/freetype_font_provider.cpp +0 -0
  184. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/text/text_metrics.cpp +0 -0
  185. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/user_calls.cpp +0 -0
  186. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/utf8.cpp +0 -0
  187. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/value.cpp +0 -0
  188. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/xkcd_colors.cpp +0 -0
  189. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/xkcd_colors.hpp +0 -0
  190. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/src/zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_booleans.cpp +0 -0
  192. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_bytecode_compiler.cpp +0 -0
  193. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_children_separate.cpp +0 -0
  194. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_cli.cpp +0 -0
  195. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_control_flow.cpp +0 -0
  196. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_coverage.cpp +0 -0
  197. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_csg_tree.cpp +0 -0
  198. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_debug_hooks.cpp +0 -0
  199. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_discretizer.cpp +0 -0
  200. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_dxf_svg_import.cpp +0 -0
  201. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_eval_context.cpp +0 -0
  202. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_export.cpp +0 -0
  203. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_expr_eval.cpp +0 -0
  204. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_extrude_roof.cpp +0 -0
  205. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_feature_detection.cpp +0 -0
  206. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_function_builtins.cpp +0 -0
  207. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_helpers.hpp +0 -0
  208. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_import_export.cpp +0 -0
  209. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_include_cache.cpp +0 -0
  210. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_manifold_cache.cpp +0 -0
  211. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_mesh_check.cpp +0 -0
  212. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_multi_color_merge.cpp +0 -0
  213. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_primitives.cpp +0 -0
  214. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_profiling.cpp +0 -0
  215. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_python_bindings.py +0 -0
  216. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_render_expr.cpp +0 -0
  217. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_scope_trail.cpp +0 -0
  218. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_scoping.cpp +0 -0
  219. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_surface.cpp +0 -0
  220. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_tail_calls.cpp +0 -0
  221. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_text.cpp +0 -0
  222. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_transforms.cpp +0 -0
  223. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_undef_args.cpp +0 -0
  224. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_unexpected_args.cpp +0 -0
  225. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_use.cpp +0 -0
  226. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_value.cpp +0 -0
  227. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_viewport_params.cpp +0 -0
  228. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tests/test_zip_stored.cpp +0 -0
  229. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tools/cli/CMakeLists.txt +0 -0
  230. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tools/cli/cli_lib.cpp +0 -0
  231. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tools/cli/cli_lib.hpp +0 -0
  232. {openscad_cpp_evaluator-1.37.1 → openscad_cpp_evaluator-1.38.0}/tools/cli/main.cpp +0 -0
@@ -507,6 +507,30 @@ Requires the same toolchain as `openscad_cpp_parser`: CMake ≥3.24, a C++20 com
507
507
  (macOS: `brew install bison`, the submodule's own CMakeLists auto-detects Homebrew's), Flex.
508
508
  GoogleTest is fetched automatically via `FetchContent`.
509
509
 
510
+ ### Building offline (Flathub)
511
+
512
+ Every dependency is a `FetchContent` download pinned to a tag, commit or checksummed tarball --
513
+ **keep it that way**: an unpinned `master` makes two builds of one tag differ and rules out a
514
+ build that lists its sources by checksum. A build with no network (Flathub's) hands each one in as
515
+ a source directory and forbids downloading:
516
+
517
+ ```bash
518
+ cmake -S . -B build -DFETCHCONTENT_FULLY_DISCONNECTED=ON -DBUILD_TESTING=OFF \
519
+ -DFETCHCONTENT_SOURCE_DIR_MANIFOLD=... -DFETCHCONTENT_SOURCE_DIR_CLIPPER2=... \
520
+ -DFETCHCONTENT_SOURCE_DIR_BOOST_CONFIG=... -DFETCHCONTENT_SOURCE_DIR_BOOST_POLYGON=... \
521
+ -DFETCHCONTENT_SOURCE_DIR_FREETYPE=... -DFETCHCONTENT_SOURCE_DIR_HARFBUZZ=... \
522
+ -DFETCHCONTENT_SOURCE_DIR_STB=... -DFETCHCONTENT_SOURCE_DIR_CPP_LINENOISE=... \
523
+ -DFETCHCONTENT_SOURCE_DIR_NLOHMANN_JSON=...
524
+ ```
525
+
526
+ That is the whole list for a wheel build (tests off; GoogleTest is the only other download, and
527
+ TBB only with `MANIFOLD_PAR`). Clipper2 is Manifold's own download, pinned in Manifold's
528
+ `cmake/manifoldDeps.cmake`; nlohmann_json is the parser's. FetchContent skips `PATCH_COMMAND` for a
529
+ supplied source, so the root `CMakeLists.txt` runs `cmake/patch_manifold.cmake` itself when
530
+ `FETCHCONTENT_SOURCE_DIR_MANIFOLD` is set -- otherwise an offline build would quietly ship the heap
531
+ overflow that patch fixes. Verified 2026-10-06: a fully disconnected configure + build produced the
532
+ Python module and CLI, and rendered text (freetype + harfbuzz).
533
+
510
534
  Run a single test binary or filter:
511
535
 
512
536
  ```bash
@@ -1842,3 +1866,33 @@ A compiled function keeps its parameters in frame slots, which no `EvalContext`
1842
1866
  can see, and the children are statement opcodes that resolve names through the
1843
1867
  context — without this, `function f(w) = render() { cube(w); }.volume;` finds no
1844
1868
  `w` and silently measures nothing.
1869
+
1870
+ ## `profile_time()`
1871
+
1872
+ `profile_time([label]) { ... }` prints `PROFILE: label: T ms (script S ms, geometry G ms)`;
1873
+ `x = profile_time([label]) expr;` prints `PROFILE: label: T ms` and evaluates to `expr`'s value.
1874
+ `supported_feature("profile-time")` is 1. The label is optional (default `line N of file`), and
1875
+ is evaluated before the clock starts. Times under 1 ms print in microseconds (`fmtMs`), so a fast part never reads `0.00 ms`.
1876
+
1877
+ ### Implementation notes
1878
+
1879
+ - **Parser:** `profile_time` is a reserved keyword (`KW_PROFILE_TIME`) for the same reason as
1880
+ `render`. The statement form is a plain `ModularCall` named `profile_time`; the expression form
1881
+ is a `ProfileTimeOp` node shaped like `EchoOp`, with a required body.
1882
+ - **Script time** is taken around the resolve function's call in `Evaluator::evalModularCall`
1883
+ (csg_resolve.cpp), not inside `resolveProfileTime`, so argument evaluation and every child
1884
+ count. It is stored on `CSGNode::profileScriptMs`, which is deliberately not in `cacheKey()` --
1885
+ a timing in the key would make every profiled subtree a permanent miss.
1886
+ - **Geometry time** is taken in `generateTreeImpl`, from *before* the cache lookup, and printed
1887
+ when the node finishes, so a hit reports `geometry cached` rather than vanishing. A hit on an
1888
+ **ancestor** never visits the profile_time() node at all; `reportCachedProfiles` walks the hit
1889
+ subtree for them, but only when the run resolved any (`profileNodeCount_`), so a model without
1890
+ profile_time() pays nothing on its hits.
1891
+ - **Resolve-only runs** (`evaluate(..., generate=false)`) print `script S ms (no geometry built)`
1892
+ at resolve time instead (`generateRequested_`).
1893
+ - **Expression form** is interpreter-only: the bytecode compiler has no case for
1894
+ `ProfileTimeOp`, so its `default:` throws `NotCompilable` and the containing function falls back
1895
+ to the interpreter. It is also deliberately absent from `simplifyTailStep` -- the clock must stop
1896
+ after the body, which a tail step would not wait for.
1897
+ - `PROFILE:` lines go straight to `echoFn_`, not through `emitWarning`, so the ManifoldCache never
1898
+ stores or replays them (a replay would print a stale time as if it were current).
@@ -141,6 +141,16 @@ FetchContent_Declare(
141
141
  # CsgLeafNode::Compose -- see cmake/patch_manifold.cmake.
142
142
  PATCH_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/patch_manifold.cmake
143
143
  )
144
+ # FetchContent runs PATCH_COMMAND only for a source it downloads itself, so
145
+ # a build handed Manifold's source (FETCHCONTENT_SOURCE_DIR_MANIFOLD, as an
146
+ # offline Flatpak build does) would otherwise compile it unpatched. The
147
+ # script is idempotent, so running it here too is safe.
148
+ if(FETCHCONTENT_SOURCE_DIR_MANIFOLD)
149
+ execute_process(
150
+ COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/patch_manifold.cmake
151
+ WORKING_DIRECTORY ${FETCHCONTENT_SOURCE_DIR_MANIFOLD}
152
+ COMMAND_ERROR_IS_FATAL ANY)
153
+ endif()
144
154
  FetchContent_MakeAvailable(manifold)
145
155
 
146
156
  # Boost.Polygon's Voronoi diagram builder, for roof()'s general (multi-
@@ -158,7 +168,13 @@ FetchContent_MakeAvailable(boost_config boost_polygon)
158
168
  # path, not add_subdirectory/MakeAvailable's target machinery. (It used to
159
169
  # supply stb_truetype.h for the font backend too; FreeType/HarfBuzz below
160
170
  # replaced that.)
161
- FetchContent_Declare(stb GIT_REPOSITORY https://github.com/nothings/stb.git GIT_TAG master GIT_SHALLOW TRUE)
171
+ #
172
+ # Pinned to a commit, as everything here is: stb has no releases, and
173
+ # following master made two builds of the same tag differ, which also rules
174
+ # out an offline (Flathub) build listing its sources by checksum.
175
+ FetchContent_Declare(stb
176
+ URL https://github.com/nothings/stb/archive/2c980bb59875b0d32144a71867fbdebb2f77cd20.tar.gz
177
+ URL_HASH SHA256=9a955b1b49a4410088a2e0ee2a9c057c3c907d0c1d75454144cb980aca0ba515)
162
178
  FetchContent_MakeAvailable(stb)
163
179
 
164
180
  # FreeType (glyph outlines + face metrics) and HarfBuzz (shaping), the two
@@ -221,7 +237,10 @@ FetchContent_MakeAvailable(harfbuzz)
221
237
  # `CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR`, so FetchContent_
222
238
  # MakeAvailable here only exposes its `linenoise` INTERFACE target, matching
223
239
  # the existing MANIFOLD_TEST-override pattern's intent for free.
224
- FetchContent_Declare(cpp_linenoise GIT_REPOSITORY https://github.com/yhirose/cpp-linenoise.git GIT_TAG master GIT_SHALLOW TRUE)
240
+ # Pinned to a commit for the same reason as stb: no releases.
241
+ FetchContent_Declare(cpp_linenoise
242
+ URL https://github.com/yhirose/cpp-linenoise/archive/b0e8b88f53be0de26252180b1b959c6dd67ec4a9.tar.gz
243
+ URL_HASH SHA256=e3e67ea401a9e6e19c300f025c51faab197b83254c83e6bef8ef452ee184548b)
225
244
  FetchContent_MakeAvailable(cpp_linenoise)
226
245
 
227
246
  # Embeds the bundled default font's (resources/fonts/) raw bytes into a
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.37.1
3
+ Version: 1.38.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -34,6 +34,7 @@ enum class NodeKind {
34
34
  LetOp,
35
35
  EchoOp,
36
36
  AssertOp,
37
+ ProfileTimeOp,
37
38
  FunctionLiteral,
38
39
  // Operators
39
40
  UnaryMinusOp,
@@ -217,6 +217,20 @@ public:
217
217
  void buildScope(Scope& parentScope) override;
218
218
  };
219
219
 
220
+ // `profile_time(arguments...) body` -- same shape as EchoOp; times the body.
221
+ // Unlike echo/assert the body is required: timing nothing is meaningless.
222
+ class ProfileTimeOp : public Expression {
223
+ public:
224
+ ProfileTimeOp(Position position, std::vector<std::unique_ptr<Argument>> arguments, std::unique_ptr<Expression> body)
225
+ : Expression(NodeKind::ProfileTimeOp, std::move(position)), arguments(std::move(arguments)), body(std::move(body)) {}
226
+
227
+ std::vector<std::unique_ptr<Argument>> arguments;
228
+ std::unique_ptr<Expression> body;
229
+
230
+ std::string toString() const override;
231
+ void buildScope(Scope& parentScope) override;
232
+ };
233
+
220
234
  // `assert(arguments...) body` -- same shape as EchoOp.
221
235
  class AssertOp : public Expression {
222
236
  public:
@@ -21,6 +21,7 @@ const char* nodeKindName(NodeKind kind) {
21
21
  case NodeKind::LetOp: return "LetOp";
22
22
  case NodeKind::EchoOp: return "EchoOp";
23
23
  case NodeKind::AssertOp: return "AssertOp";
24
+ case NodeKind::ProfileTimeOp: return "ProfileTimeOp";
24
25
  case NodeKind::FunctionLiteral: return "FunctionLiteral";
25
26
  case NodeKind::UnaryMinusOp: return "UnaryMinusOp";
26
27
  case NodeKind::AdditionOp: return "AdditionOp";
@@ -134,6 +134,18 @@ void EchoOp::buildScope(Scope& parentScope) {
134
134
  body->buildScope(parentScope);
135
135
  }
136
136
 
137
+ std::string ProfileTimeOp::toString() const {
138
+ return "profile_time(" + joinToString(arguments, ", ") + ") " + body->toString();
139
+ }
140
+
141
+ void ProfileTimeOp::buildScope(Scope& parentScope) {
142
+ setScope(parentScope);
143
+ for (auto& a : arguments) {
144
+ a->buildScope(parentScope);
145
+ }
146
+ body->buildScope(parentScope);
147
+ }
148
+
137
149
  std::string AssertOp::toString() const {
138
150
  return "assert(" + joinToString(arguments, ", ") + ") " + body->toString();
139
151
  }
@@ -219,6 +231,7 @@ int operatorPrecedence(NodeKind kind) {
219
231
  case NodeKind::LetOp:
220
232
  case NodeKind::EchoOp:
221
233
  case NodeKind::AssertOp:
234
+ case NodeKind::ProfileTimeOp:
222
235
  case NodeKind::FunctionLiteral:
223
236
  return 0;
224
237
  case NodeKind::LogicalOrOp:
@@ -88,6 +88,11 @@ NodePtr makeEchoOp(ParserDriver& driver, const OscadLocation& loc, NodeList argu
88
88
  nodeCast<Expression>(std::move(body)));
89
89
  }
90
90
 
91
+ NodePtr makeProfileTimeOp(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodePtr body) {
92
+ return std::make_unique<ProfileTimeOp>(driver.toPosition(loc), nodeListCast<Argument>(std::move(arguments)),
93
+ nodeCast<Expression>(std::move(body)));
94
+ }
95
+
91
96
  NodePtr makeAssertOp(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodePtr body) {
92
97
  return std::make_unique<AssertOp>(driver.toPosition(loc), nodeListCast<Argument>(std::move(arguments)),
93
98
  nodeCast<Expression>(std::move(body)));
@@ -104,6 +104,7 @@ NodePtr makeAssignment(ParserDriver& driver, const OscadLocation& loc, const Osc
104
104
 
105
105
  NodePtr makeLetOp(ParserDriver& driver, const OscadLocation& loc, NodeList assignments, NodePtr body);
106
106
  NodePtr makeEchoOp(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodePtr body);
107
+ NodePtr makeProfileTimeOp(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodePtr body);
107
108
  NodePtr makeAssertOp(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodePtr body);
108
109
  NodePtr makeFunctionLiteral(ParserDriver& driver, const OscadLocation& loc, NodeList parameters, NodePtr body);
109
110
 
@@ -52,6 +52,9 @@ const std::unordered_map<std::string, KeywordFactory>& keywordTable() {
52
52
  // apart from a function call. Note `$render` still lexes as NAME --
53
53
  // this table is keyed on the full IDENT text, including the `$`.
54
54
  {"render", [](const OscadLocation& l) { return yy::parser::make_KW_RENDER(l); }},
55
+ // Reserved for the same reason as `render`: its expression form
56
+ // (`x = profile_time("label") expr;`) starts like a function call.
57
+ {"profile_time", [](const OscadLocation& l) { return yy::parser::make_KW_PROFILE_TIME(l); }},
55
58
  {"undef", [](const OscadLocation& l) { return yy::parser::make_KW_UNDEF(l); }},
56
59
  {"true", [](const OscadLocation& l) { return yy::parser::make_KW_TRUE(l); }},
57
60
  {"false", [](const OscadLocation& l) { return yy::parser::make_KW_FALSE(l); }},
@@ -93,6 +93,7 @@
93
93
  KW_INTERSECTION_FOR "intersection_for"
94
94
  KW_EACH "each"
95
95
  KW_RENDER "render"
96
+ KW_PROFILE_TIME "profile_time"
96
97
  KW_UNDEF "undef"
97
98
  KW_TRUE "true"
98
99
  KW_FALSE "false"
@@ -158,7 +159,7 @@
158
159
  %type <NodePtr> modifier_show_only modifier_highlight modifier_background modifier_disable
159
160
  %type <NodePtr> if_statement ifelse_statement
160
161
  %type <NodePtr> modular_for modular_intersection_for modular_let modular_assert modular_echo modular_call
161
- %type <NodePtr> render_stmt render_expr
162
+ %type <NodePtr> render_stmt render_expr profile_time_stmt
162
163
  %type <NodePtr> expr opchain postfix primary
163
164
  %type <NodePtr> range_expr vector_expr vector_element
164
165
  %type <NodePtr> listcomp_elements listcomp_paren_expr listcomp_let listcomp_each
@@ -330,6 +331,7 @@ single_module_instantiation:
330
331
  | modular_echo { $$ = std::move($1); }
331
332
  | modular_call { $$ = std::move($1); }
332
333
  | render_stmt { $$ = std::move($1); }
334
+ | profile_time_stmt { $$ = std::move($1); }
333
335
  ;
334
336
 
335
337
  modular_for:
@@ -378,6 +380,14 @@ render_stmt:
378
380
  }
379
381
  ;
380
382
 
383
+ // Same arrangement as render_stmt: the keyword exists only for the
384
+ // expression form, and the statement form is an ordinary builtin call.
385
+ profile_time_stmt:
386
+ "profile_time" "(" arguments ")" child_statement {
387
+ $$ = makeModularCall(driver, @$, @1, "profile_time", std::move($3), std::move($5));
388
+ }
389
+ ;
390
+
381
391
  // -- Expressions ----------------------------------------------------------
382
392
  //
383
393
  // `expr` covers let/assert/echo/funclit_def/ternary plus the operator
@@ -393,6 +403,7 @@ expr:
393
403
  | "assert" "(" arguments ")" { $$ = makeAssertOp(driver, @$, std::move($3), makeUndefinedLiteral(driver, @$)); }
394
404
  | "echo" "(" arguments ")" expr { $$ = makeEchoOp(driver, @$, std::move($3), std::move($5)); }
395
405
  | "echo" "(" arguments ")" { $$ = makeEchoOp(driver, @$, std::move($3), makeUndefinedLiteral(driver, @$)); }
406
+ | "profile_time" "(" arguments ")" expr { $$ = makeProfileTimeOp(driver, @$, std::move($3), std::move($5)); }
396
407
  | "function" "(" parameters ")" expr { $$ = makeFunctionLiteral(driver, @$, std::move($3), std::move($5)); }
397
408
  | opchain "?" expr ":" expr { $$ = makeTernaryOp(driver, @$, std::move($1), std::move($3), std::move($5)); }
398
409
  | opchain { $$ = std::move($1); }
@@ -180,6 +180,12 @@ void classifyNode(ASTNode& node, std::vector<ExprSlot>& exprFields, std::vector<
180
180
  addExpr(n.body, exprFields);
181
181
  break;
182
182
  }
183
+ case NodeKind::ProfileTimeOp: {
184
+ auto& n = static_cast<ProfileTimeOp&>(node);
185
+ addArgumentExprList(n.arguments, exprFields);
186
+ addExpr(n.body, exprFields);
187
+ break;
188
+ }
183
189
  case NodeKind::AssertOp: {
184
190
  auto& n = static_cast<AssertOp&>(node);
185
191
  addArgumentExprList(n.arguments, exprFields);
@@ -486,6 +486,9 @@ std::string fmtExpr(const ASTNode& exprNode, int indent, int w) {
486
486
  }
487
487
  return "echo(" + args + ")\n" + pad + fmtExpr(*e->body, indent, w);
488
488
  }
489
+ if (auto* p = dynamic_cast<const ProfileTimeOp*>(&exprNode)) {
490
+ return "profile_time(" + joinToString(p->arguments, ", ") + ")\n" + pad + fmtExpr(*p->body, indent, w);
491
+ }
489
492
  if (auto* l = dynamic_cast<const LetOp*>(&exprNode)) {
490
493
  return fmtLetOpExpr(*l, indent, w);
491
494
  }
@@ -150,6 +150,12 @@ json toJsonImpl(const ASTNode& node, bool includePos) {
150
150
  j["body"] = valueToJson(n.body.get(), includePos);
151
151
  break;
152
152
  }
153
+ case NodeKind::ProfileTimeOp: {
154
+ auto& n = static_cast<const ProfileTimeOp&>(node);
155
+ j["arguments"] = listToJson(n.arguments, includePos);
156
+ j["body"] = valueToJson(n.body.get(), includePos);
157
+ break;
158
+ }
153
159
  case NodeKind::AssertOp: {
154
160
  auto& n = static_cast<const AssertOp&>(node);
155
161
  j["arguments"] = listToJson(n.arguments, includePos);
@@ -446,6 +452,11 @@ const std::unordered_map<std::string, Builder>& registry() {
446
452
  return std::make_unique<EchoOp>(std::move(pos), listFromJson<Argument>(j, "arguments"),
447
453
  childFromJson<Expression>(j, "body"));
448
454
  }},
455
+ {"ProfileTimeOp",
456
+ [](const json& j, Position pos) -> std::unique_ptr<ASTNode> {
457
+ return std::make_unique<ProfileTimeOp>(std::move(pos), listFromJson<Argument>(j, "arguments"),
458
+ childFromJson<Expression>(j, "body"));
459
+ }},
449
460
  {"AssertOp",
450
461
  [](const json& j, Position pos) -> std::unique_ptr<ASTNode> {
451
462
  return std::make_unique<AssertOp>(std::move(pos), listFromJson<Argument>(j, "arguments"),
@@ -26,6 +26,7 @@ add_executable(oscad_tests
26
26
  test_ast_generation.cpp
27
27
  test_node_str.cpp
28
28
  test_render_expression.cpp
29
+ test_profile_time.cpp
29
30
  test_strict_commas.cpp
30
31
  test_concurrent_parse.cpp
31
32
  )
@@ -0,0 +1,86 @@
1
+ // `profile_time()`: the statement form times its children, the expression
2
+ // form (`x = profile_time("label") expr;`) its body. `profile_time` is a
3
+ // reserved keyword only because the expression form would otherwise be
4
+ // indistinguishable from a function call; the statement form is a plain
5
+ // ModularCall, so builtin dispatch downstream sees an ordinary module.
6
+
7
+ #include "openscad_cpp_parser/api.hpp"
8
+ #include "openscad_cpp_parser/pretty_print.hpp"
9
+ #include "openscad_cpp_parser/serialization.hpp"
10
+ #include "test_helpers.hpp"
11
+
12
+ #include <gtest/gtest.h>
13
+
14
+ using namespace oscad;
15
+
16
+ namespace {
17
+
18
+ const ProfileTimeOp* profileOf(const std::vector<std::unique_ptr<ASTNode>>& ast) {
19
+ auto* a = dynamic_cast<Assignment*>(ast[0].get());
20
+ return a ? dynamic_cast<const ProfileTimeOp*>(a->expr.get()) : nullptr;
21
+ }
22
+
23
+ } // namespace
24
+
25
+ TEST(ProfileTime, StatementFormIsAPlainModularCall) {
26
+ auto ast = parseSrc("profile_time(\"gears\") { cube(1); sphere(2); }");
27
+ ASSERT_EQ(ast.size(), 1u);
28
+ auto* call = dynamic_cast<ModularCall*>(ast[0].get());
29
+ ASSERT_NE(call, nullptr);
30
+ EXPECT_EQ(call->name->name, "profile_time");
31
+ EXPECT_EQ(call->arguments.size(), 1u);
32
+ EXPECT_EQ(call->children.size(), 2u);
33
+ }
34
+
35
+ TEST(ProfileTime, ExpressionFormWrapsItsBody) {
36
+ auto ast = parseSrc("x = profile_time(\"sum\") 1 + 2;");
37
+ const ProfileTimeOp* p = profileOf(ast);
38
+ ASSERT_NE(p, nullptr);
39
+ EXPECT_EQ(p->kind(), NodeKind::ProfileTimeOp);
40
+ EXPECT_EQ(p->arguments.size(), 1u);
41
+ EXPECT_EQ(p->body->kind(), NodeKind::AdditionOp);
42
+ }
43
+
44
+ TEST(ProfileTime, ExpressionFormAllowsNoLabel) {
45
+ const ProfileTimeOp* p = nullptr;
46
+ auto ast = parseSrc("x = profile_time() f(3);");
47
+ p = profileOf(ast);
48
+ ASSERT_NE(p, nullptr);
49
+ EXPECT_TRUE(p->arguments.empty());
50
+ }
51
+
52
+ TEST(ProfileTime, ExpressionFormNeedsABody) {
53
+ EXPECT_THROW(parseSrc("x = profile_time(\"a\");"), ParseError);
54
+ }
55
+
56
+ TEST(ProfileTime, IsReserved) {
57
+ EXPECT_THROW(parseSrc("profile_time = 1;"), ParseError);
58
+ EXPECT_THROW(parseSrc("function profile_time() = 1;"), ParseError);
59
+ EXPECT_THROW(parseSrc("module profile_time() {}"), ParseError);
60
+ // `$profile_time` is a different identifier and stays usable.
61
+ EXPECT_NO_THROW(parseSrc("$profile_time = 1;"));
62
+ }
63
+
64
+ TEST(ProfileTime, ToStringRoundTrips) {
65
+ auto ast = parseSrc("x = profile_time(\"a\") 1 + 2;");
66
+ EXPECT_EQ(ast[0]->toString(), "x = profile_time(\"a\") 1 + 2");
67
+ EXPECT_NE(profileOf(parseSrc(ast[0]->toString() + ";")), nullptr);
68
+ }
69
+
70
+ TEST(ProfileTime, JsonRoundTrips) {
71
+ auto ast = parseSrc("x = profile_time(\"a\") 1 + 2;");
72
+ auto back = astFromJsonString(astToJsonString(ast));
73
+ const ProfileTimeOp* p = profileOf(back);
74
+ ASSERT_NE(p, nullptr);
75
+ EXPECT_EQ(p->body->kind(), NodeKind::AdditionOp);
76
+ }
77
+
78
+ TEST(ProfileTime, PrettyPrintReparses) {
79
+ for (const char* src : {"x = profile_time(\"a\") 1 + 2;",
80
+ "profile_time(\"g\") { cube(1); }"}) {
81
+ auto ast = parseSrc(src);
82
+ std::string out = toOpenscad(ast);
83
+ EXPECT_NE(out.find("profile_time(\""), std::string::npos) << out;
84
+ EXPECT_NO_THROW(parseSrc(out)) << out;
85
+ }
86
+ }
@@ -71,6 +71,13 @@ struct CSGNode {
71
71
  // stack risk the moment this node is ever destroyed.
72
72
  int treeDepth = 1;
73
73
 
74
+ // profile_time() only: how long resolving this node (its arguments and
75
+ // its children's script code) took, in ms; < 0 for every other node.
76
+ // Measured fresh on every run, so -- like warnEntry -- deliberately NOT
77
+ // part of cacheKey(): putting a timing in the key would make every
78
+ // profiled subtree a permanent cache miss.
79
+ double profileScriptMs = -1.0;
80
+
74
81
  // Memoizes manifold_cache.cpp's cacheKey(*this) -- that function
75
82
  // recurses into every descendant to build its own key, so without this
76
83
  // memo a generate-time walk that calls cacheKey() at every node (not
@@ -422,6 +422,22 @@ public:
422
422
  // subtree, not just direct children, since hull() consumes all of it.
423
423
  bool insideHull = false;
424
424
 
425
+ // profile_time(). generateRequested_ is false only inside an
426
+ // evaluate(generate = false) run, where no geometry pass follows to
427
+ // report a profiled block, so resolve reports the script time alone.
428
+ // profileNodeCount_ counts this run's profile_time() nodes so a cache
429
+ // hit only walks its subtree for them when there are any (see
430
+ // reportCachedProfiles).
431
+ bool generateRequested_ = true;
432
+ std::size_t profileNodeCount_ = 0;
433
+ // Prints "PROFILE: <label>: ..." for a profile_time() node. geometryMs
434
+ // < 0 means no geometry pass; `cached` means its geometry came from the
435
+ // ManifoldCache rather than being built.
436
+ void reportProfile(const CSGNode& node, double geometryMs, bool cached);
437
+ // A cache hit at `node` skips its whole subtree, so a profile_time()
438
+ // inside it would otherwise print nothing at all.
439
+ void reportCachedProfiles(const CSGNode& node);
440
+
425
441
  ColoredBody tagGenerated(manifold::Manifold body, const oscad::ASTNode& node, const Value& colorValue);
426
442
 
427
443
  // tagGenerated()'s counterpart for a mesh Manifold refused to build --
@@ -837,6 +853,7 @@ private:
837
853
  Value evalLetExpr(const oscad::LetOp& node, EvalContext& ctx);
838
854
  Value evalEchoExpr(const oscad::EchoOp& node, EvalContext& ctx);
839
855
  Value evalAssertExpr(const oscad::AssertOp& node, EvalContext& ctx);
856
+ Value evalProfileTimeExpr(const oscad::ProfileTimeOp& node, EvalContext& ctx);
840
857
  void bindLetName(EvalContext& ctx, const std::string& name, const Value& v);
841
858
 
842
859
  // Bracketed public in place: Stage 2's Op::NativeStatement
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.37.1"
7
+ version = "1.38.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -43,6 +43,7 @@ add_library(openscad_cpp_evaluator STATIC
43
43
  builtins/booleans.cpp
44
44
  builtins/modifiers.cpp
45
45
  builtins/control.cpp
46
+ builtins/profile_time.cpp
46
47
  builtins/function_builtins.cpp
47
48
  builtins/import.cpp
48
49
  builtins/topology.cpp
@@ -146,6 +146,12 @@ std::vector<ColoredBody> generateChildren(Evaluator& ev, const CSGParams& params
146
146
  // generateTree()'s default child-concatenation, exactly reproducing
147
147
  // passthrough).
148
148
  CSGParams resolveRender(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
149
+ CSGParams resolveProfileTime(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
150
+ // profile_time()'s message pieces: "12.40 ms" (or "4.7 µs" under 1 ms), and the label it names --
151
+ // the label as written (a non-string formatted like echo() would), or
152
+ // "line N" (with the file when there is one) when none was given.
153
+ std::string fmtMs(double ms);
154
+ std::string profileLabel(const Value& label, const oscad::Position& pos);
149
155
 
150
156
  // breakpoint() -- no-op as of Phase 4 (no debugger until Phase 9); still
151
157
  // evaluates/validates its optional `condition` argument for parity.
@@ -1018,6 +1018,7 @@ const std::unordered_map<std::string, double>& featureLevels() {
1018
1018
  {"roof-op", 1.0}, // roof(), method="voronoi" only
1019
1019
  {"svg-class", 1.0}, // import(svg, class=); id= is upstream's
1020
1020
  {"discretization-by-error", 1.0}, // $fe, always on (upstream: behind --enable)
1021
+ {"profile-time", 1.0}, // profile_time(label) { ... } and profile_time(label) expr
1021
1022
  };
1022
1023
  return levels;
1023
1024
  }
@@ -0,0 +1,63 @@
1
+ #include "builtins.hpp"
2
+
3
+ #include "openscad_cpp_evaluator/call_args.hpp"
4
+ #include "openscad_cpp_evaluator/evaluator.hpp"
5
+
6
+ #include <cstdio>
7
+
8
+ namespace oscadeval {
9
+
10
+ // Microseconds below a millisecond, where "0.00 ms" would hide the answer.
11
+ std::string fmtMs(double ms) {
12
+ char buf[32];
13
+ if (ms < 1.0) std::snprintf(buf, sizeof buf, "%.1f \u00b5s", ms * 1000.0);
14
+ else std::snprintf(buf, sizeof buf, "%.2f ms", ms);
15
+ return buf;
16
+ }
17
+
18
+ std::string profileLabel(const Value& label, const oscad::Position& pos) {
19
+ if (const std::string* s = std::get_if<std::string>(&label)) return *s;
20
+ if (!std::holds_alternative<std::monostate>(label)) return fmtValue(label);
21
+ std::string where = "line " + std::to_string(pos.line);
22
+ if (!pos.origin.empty()) where += " of " + pos.origin.substr(pos.origin.find_last_of("/\\") + 1);
23
+ return where;
24
+ }
25
+
26
+ // profile_time([label]) { ... } -- resolves its children like render() does.
27
+ // The script-time clock is NOT here but around this call in
28
+ // Evaluator::evalModularCall, so it covers argument evaluation and every
29
+ // child alike; the geometry time is taken in generateTreeImpl. The label
30
+ // goes into params (it is constant, so it costs the cache nothing).
31
+ CSGParams resolveProfileTime(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
32
+ auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
33
+ CSGParams params;
34
+ params["label"] = getArg(args, 0, "label");
35
+ EvalContext blockCtx = ev.blockScope(effCtx);
36
+ ev.evalChildren(node.children, blockCtx);
37
+ return params;
38
+ }
39
+
40
+ void Evaluator::reportProfile(const CSGNode& node, double geometryMs, bool cached) {
41
+ if (!echoFn_ || node.node == nullptr) return;
42
+ auto it = node.params.find("label");
43
+ const std::string label = profileLabel(it == node.params.end() ? Value{} : it->second, node.node->position());
44
+ const double scriptMs = node.profileScriptMs < 0 ? 0.0 : node.profileScriptMs;
45
+ std::string msg = "PROFILE: " + label + ": ";
46
+ if (geometryMs < 0) {
47
+ msg += "script " + fmtMs(scriptMs) + " (no geometry built)";
48
+ } else if (cached) {
49
+ msg += fmtMs(scriptMs) + " (script " + fmtMs(scriptMs) + ", geometry cached)";
50
+ } else {
51
+ msg += fmtMs(scriptMs + geometryMs) + " (script " + fmtMs(scriptMs) + ", geometry " + fmtMs(geometryMs) + ")";
52
+ }
53
+ echoFn_(msg);
54
+ }
55
+
56
+ void Evaluator::reportCachedProfiles(const CSGNode& node) {
57
+ for (const std::unique_ptr<CSGNode>& c : node.children) {
58
+ if (c->isBuiltin && c->kind == "profile_time") reportProfile(*c, 0.0, true);
59
+ reportCachedProfiles(*c);
60
+ }
61
+ }
62
+
63
+ } // namespace oscadeval
@@ -26,6 +26,7 @@ const std::unordered_map<std::string_view, ResolveFn>& resolveDispatch() {
26
26
  {"intersection", &resolveCsg},
27
27
  {"children", &resolveChildren},
28
28
  {"render", &resolveRender},
29
+ {"profile_time", &resolveProfileTime},
29
30
  {"breakpoint", &resolveBreakpoint},
30
31
  {"import", &resolveImport},
31
32
  {"hull", &resolveHull},
@@ -118,6 +119,7 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
118
119
  {"minkowski", {"convexity"}},
119
120
  {"children", {"index", "separate"}},
120
121
  {"render", {"convexity"}},
122
+ {"profile_time", {"label"}},
121
123
  // "repair" and "class" are this port's own additions, not upstream
122
124
  // parameters. ("id" IS upstream's, and was declared here long before
123
125
  // anything read it -- so a script filtering by id was accepted in
@@ -263,6 +263,12 @@ private:
263
263
  expr(*o.body);
264
264
  break;
265
265
  }
266
+ case NodeKind::ProfileTimeOp: {
267
+ auto& o = static_cast<const oscad::ProfileTimeOp&>(e);
268
+ args(o.arguments);
269
+ expr(*o.body);
270
+ break;
271
+ }
266
272
  case NodeKind::FunctionLiteral: {
267
273
  auto& f = static_cast<const oscad::FunctionLiteral&>(e);
268
274
  emit(&e, CoverageKind::Body);
@@ -36,6 +36,10 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
36
36
  for (CSGNode* nodePtr : tree) {
37
37
  CSGNode& node = *nodePtr;
38
38
  checkCancel();
39
+ // profile_time(): timed from before the cache lookup, so a hit
40
+ // still reports (as cached) rather than vanishing.
41
+ const bool profiled = node.isBuiltin && node.kind == "profile_time";
42
+ const auto profileStart = profiled ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
39
43
 
40
44
  // ManifoldCache lookup: node.uncacheable (rands() taint, set
41
45
  // during resolve -- see csg_resolve.cpp) always forces a miss,
@@ -69,6 +73,8 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
69
73
  // one (BelfrySCAD #521). Goes through emitWarning so an enclosing
70
74
  // capture records them too, exactly as a fresh generate would.
71
75
  for (const std::string& w : cached->warnings) emitWarning(w);
76
+ // A hit skips the subtree, profile_time() calls and all.
77
+ if (profileNodeCount_ > 0) reportCachedProfiles(node);
72
78
  // Skipped entirely while measuring: restampCachedIds writes
73
79
  // idToNode/idToColor, and geometry that is about to be discarded
74
80
  // has no click-to-source identity worth recording. See
@@ -171,6 +177,11 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
171
177
  // capacity, and 35K nodes' worth of 592-byte slots was 230MB.
172
178
  for (const std::unique_ptr<CSGNode>& c : node.children) c->bodies = std::vector<ColoredBody>();
173
179
  }
180
+ if (profiled) {
181
+ reportProfile(node,
182
+ std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - profileStart).count(),
183
+ cached.has_value());
184
+ }
174
185
  for (const ColoredBody& b : node.bodies) topLevelBodies.push_back(b);
175
186
  }
176
187
  return topLevelBodies;