openscad-cpp-evaluator 0.54.0__tar.gz → 0.55.1__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 (208) hide show
  1. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/CLAUDE.md +11 -0
  2. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/csg_node.hpp +15 -0
  4. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/evaluator.hpp +9 -0
  5. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/import.cpp +7 -4
  7. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/primitives_3d.cpp +6 -4
  8. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/topology.cpp +38 -3
  9. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/csg_generate.cpp +15 -1
  10. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/manifold_cache.cpp +38 -1
  11. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_csg_tree.cpp +78 -0
  12. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_manifold_cache.cpp +59 -0
  13. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/.github/workflows/ci.yml +0 -0
  14. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/.github/workflows/wheels.yml +0 -0
  15. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/.gitignore +0 -0
  16. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/.gitmodules +0 -0
  17. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/CMakeLists.txt +0 -0
  18. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/LICENSE +0 -0
  19. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/README.md +0 -0
  20. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/bindings/CMakeLists.txt +0 -0
  21. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/bindings/ast_to_py.cpp +0 -0
  22. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/bindings/module.cpp +0 -0
  23. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/cmake/embed_font.cmake +0 -0
  24. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/examples/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/examples/eval_perf_benchmark.cpp +0 -0
  26. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/examples/manifold_cache_reuse.cpp +0 -0
  27. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/examples/minimal_debugger.cpp +0 -0
  28. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  29. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/.gitignore +0 -0
  30. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  31. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  32. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/README.md +0 -0
  33. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  34. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  35. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  36. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  37. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  38. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  39. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  40. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  41. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  42. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  43. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  44. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  45. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  46. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  47. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  48. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  49. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/api.cpp +0 -0
  50. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  51. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  52. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  53. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  54. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  55. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  56. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  57. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  58. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  59. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  60. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  61. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  62. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  63. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  64. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  65. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  66. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/position.cpp +0 -0
  67. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  68. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  69. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  70. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  71. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  72. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  73. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  74. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  75. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  76. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  77. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  78. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  79. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  80. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  81. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  82. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  83. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  84. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  85. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  86. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  87. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  88. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  89. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  90. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  91. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  92. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  93. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  94. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  95. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  96. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  97. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  98. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  99. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  100. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  101. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  102. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  103. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  104. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  105. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  106. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  107. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  108. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  109. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/export.hpp +0 -0
  110. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  111. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  112. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  113. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  114. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  115. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  116. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  117. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  118. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  119. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  120. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  121. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  122. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  123. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  124. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/value.hpp +0 -0
  125. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  126. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/python/openscad_cpp_evaluator/__init__.py +0 -0
  127. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/resources/fonts/LICENSE +0 -0
  128. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  129. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/CMakeLists.txt +0 -0
  130. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/booleans.cpp +0 -0
  131. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/builtins.hpp +0 -0
  132. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/call_args.cpp +0 -0
  133. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/color.cpp +0 -0
  134. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/control.cpp +0 -0
  135. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/dxf_dim.cpp +0 -0
  136. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/extrude.cpp +0 -0
  137. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/function_builtins.cpp +0 -0
  138. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/modifiers.cpp +0 -0
  139. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/primitives_2d.cpp +0 -0
  140. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/registry.cpp +0 -0
  141. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/roof.cpp +0 -0
  142. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/segments.cpp +0 -0
  143. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/surface.cpp +0 -0
  144. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/text.cpp +0 -0
  145. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/builtins/transforms.cpp +0 -0
  146. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/bytecode_compiler.cpp +0 -0
  147. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/bytecode_vm.cpp +0 -0
  148. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/colored_body.cpp +0 -0
  149. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/csg_resolve.cpp +0 -0
  150. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/css_colors.cpp +0 -0
  151. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/debug/debug_repl.cpp +0 -0
  152. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/debug_profile.cpp +0 -0
  153. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/eval_context.cpp +0 -0
  154. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/eval_error.cpp +0 -0
  155. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/eval_use.cpp +0 -0
  156. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/export.cpp +0 -0
  157. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/expr_eval.cpp +0 -0
  158. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/import/dxf_import.cpp +0 -0
  159. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/import/mesh_import.cpp +0 -0
  160. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/import/surface_load.cpp +0 -0
  161. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/import/svg_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/measure_geometry.cpp +0 -0
  163. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/mesh_check.cpp +0 -0
  164. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/stmt_eval.cpp +0 -0
  165. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/text/font_match.cpp +0 -0
  166. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/text/freetype_font_provider.cpp +0 -0
  167. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/text/text_metrics.cpp +0 -0
  168. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/user_calls.cpp +0 -0
  169. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/value.cpp +0 -0
  170. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/src/zip_stored.cpp +0 -0
  171. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/CMakeLists.txt +0 -0
  172. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_booleans.cpp +0 -0
  173. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_bytecode_compiler.cpp +0 -0
  174. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_children_separate.cpp +0 -0
  175. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_cli.cpp +0 -0
  176. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_control_flow.cpp +0 -0
  177. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_debug_hooks.cpp +0 -0
  178. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_dxf_svg_import.cpp +0 -0
  179. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_eval_context.cpp +0 -0
  180. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_export.cpp +0 -0
  181. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_expr_eval.cpp +0 -0
  182. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_extrude_roof.cpp +0 -0
  183. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_feature_detection.cpp +0 -0
  184. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_function_builtins.cpp +0 -0
  185. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_helpers.hpp +0 -0
  186. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_import_export.cpp +0 -0
  187. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_mesh_check.cpp +0 -0
  188. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_multi_color_merge.cpp +0 -0
  189. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_primitives.cpp +0 -0
  190. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_profiling.cpp +0 -0
  191. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_python_bindings.py +0 -0
  192. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_render_expr.cpp +0 -0
  193. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_scope_trail.cpp +0 -0
  194. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_scoping.cpp +0 -0
  195. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_surface.cpp +0 -0
  196. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_tail_calls.cpp +0 -0
  197. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_text.cpp +0 -0
  198. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_transforms.cpp +0 -0
  199. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_unexpected_args.cpp +0 -0
  200. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_use.cpp +0 -0
  201. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_value.cpp +0 -0
  202. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_viewport_params.cpp +0 -0
  203. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tests/test_zip_stored.cpp +0 -0
  204. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tools/cli/CMakeLists.txt +0 -0
  205. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tools/cli/cli_lib.cpp +0 -0
  206. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tools/cli/cli_lib.hpp +0 -0
  207. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/tools/cli/main.cpp +0 -0
  208. {openscad_cpp_evaluator-0.54.0 → openscad_cpp_evaluator-0.55.1}/uv.lock +0 -0
@@ -699,6 +699,17 @@ grep for `ponytail:`.
699
699
  permanent cache miss for any subtree that called `rands()` while resolving, since such a
700
700
  subtree's resolved params aren't a pure function of its own content anymore (the actual `rands()`
701
701
  output also depends on every earlier `rands()` call's position in the whole evaluation's order).
702
+ A second, separate taint — `CSGNode::keyHasClosure`, set by `cacheKey()` itself rather than at
703
+ resolve time — does the same for any subtree holding a **function literal** in its params (the
704
+ `levelset(function (x,y,z) ..., ...)` shape). `canonValue()` can only key a closure by its AST
705
+ node's raw address, which is meaningful for exactly one parse: the next render frees that AST and
706
+ a fresh closure over *different* captured values can be allocated at the very same address, so
707
+ the key silently hits the previous render's geometry — the GUI symptom was an edited script
708
+ rendering its **old** shape until saved a second time. (The address also ignores the captured
709
+ values themselves, so two closures over one literal collide within a single render, which is what
710
+ `tests/test_manifold_cache.cpp` pins down deterministically.) Keying a closure honestly means
711
+ canonicalizing its captured `let_` trail, which is type-erased (`Closure::capturedLet` is a
712
+ `shared_ptr<void>`); until that exists such a subtree simply does not participate in the cache.
702
713
  Builtins are registered in `src/builtins/registry.cpp` as `(name string) -> (ResolveFn,
703
714
  GenerateFn)` pairs — `dispatch.hpp` has the exact reasoning for `GenerateFn`'s `Evaluator&`
704
715
  parameter (provenance-table writes only, no `EvalContext`/dynamic-scope access, so the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.54.0
3
+ Version: 0.55.1
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -72,6 +72,21 @@ struct CSGNode {
72
72
  // on first cacheKey() call; every node here is always reached through a
73
73
  // non-const CSGNode&, so no `mutable` is needed to write it.
74
74
  std::optional<std::string> cachedKey;
75
+
76
+ // Set by the same cacheKey() pass that fills cachedKey: true if this
77
+ // node's own params, or any descendant's, hold a function literal.
78
+ //
79
+ // canonValue() can only key a closure by its AST node's raw address,
80
+ // and that address is meaningful for exactly one parse: the next
81
+ // render frees that AST, and a fresh closure over different captured
82
+ // values can be allocated at the very same address -- so the key
83
+ // silently hits the PREVIOUS render's geometry. (It also ignores the
84
+ // captured values themselves, so two closures over one literal share a
85
+ // key within a single render.) Keying a closure honestly would mean
86
+ // canonicalising its captured `let_` trail, which is type-erased; until
87
+ // that exists, such a subtree just does not participate in the cache.
88
+ // See csg_generate.cpp's lookup.
89
+ bool keyHasClosure = false;
75
90
  };
76
91
 
77
92
 
@@ -294,6 +294,15 @@ public:
294
294
  // depth-first on one thread.
295
295
  const oscad::Position* generateWarnEntry = nullptr;
296
296
 
297
+ // Set while generating anything beneath a hull(). An open mesh there is
298
+ // not a mistake to report: a convex hull needs only points, so BOSL2's
299
+ // hull_points(pts, fast=true) hands polyhedron() a deliberately garbage
300
+ // face list purely to carry the vertices in. The reference says nothing
301
+ // about it, and spheroid(circum=true, style="icosa") goes through that
302
+ // path -- warning would fire on an everyday BOSL2 call. Covers the whole
303
+ // subtree, not just direct children, since hull() consumes all of it.
304
+ bool insideHull = false;
305
+
297
306
  ColoredBody tagGenerated(manifold::Manifold body, const oscad::ASTNode& node, const Value& colorValue);
298
307
 
299
308
  // tagGenerated()'s counterpart for a mesh Manifold refused to build --
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.54.0"
7
+ version = "0.55.1"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -318,10 +318,13 @@ std::vector<ColoredBody> generateImport(Evaluator& ev, const CSGParams& params,
318
318
  const MeshDiagnosis d = checkMesh(mesh);
319
319
  std::string why = d.summary();
320
320
  if (why.empty()) why = manifoldErrorName(body.Status());
321
- ev.warn("import: mesh is not a closed solid (" + why +
322
- "); drawing it as a surface, but it cannot take part in any CSG "
323
- "operation" + (repair ? "" : ". Try import(..., repair=true)"),
324
- &node.position());
321
+ if (!ev.insideHull) {
322
+ ev.warn("import: mesh is not a closed solid (" + why +
323
+ "); drawing it as a surface. hull() can still use its points, but "
324
+ "it cannot take part in union/difference/intersection" +
325
+ std::string(repair ? "" : ". Try import(..., repair=true)"),
326
+ &node.position());
327
+ }
325
328
  // tagDisplayOnly carries a raw triangle soup for the renderer to
326
329
  // draw, and the renderer is float either way, so narrowing here
327
330
  // costs nothing this mesh will ever be measured on.
@@ -802,10 +802,12 @@ std::vector<ColoredBody> generatePolyhedron(Evaluator& ev, const CSGParams& para
802
802
  // closed mesh with reversed winding, non-manifold vertices or
803
803
  // self-intersections builds perfectly well, and only an OPEN one
804
804
  // reaches this branch. The count points straight at the problem.
805
- ev.warn("polyhedron: mesh is not closed -- " + std::to_string(countBoundaryEdges(mesh)) +
806
- " boundary edge(s); drawing it as a surface, but it cannot "
807
- "take part in any CSG operation",
808
- &node.position());
805
+ if (!ev.insideHull) {
806
+ ev.warn("polyhedron: mesh is not closed -- " + std::to_string(countBoundaryEdges(mesh)) +
807
+ " boundary edge(s); drawing it as a surface. hull() can still "
808
+ "use its points, but it cannot take part in union/difference/intersection",
809
+ &node.position());
810
+ }
809
811
  manifold::MeshGL soup;
810
812
  soup.numProp = 3;
811
813
  soup.vertProperties.assign(mesh.vertProperties.begin(), mesh.vertProperties.end());
@@ -35,19 +35,51 @@ CSGParams resolveHull(Evaluator& ev, const oscad::ModularCall& node, EvalContext
35
35
  // matching the reference's own `if bodies_3d: ... else: sections...`
36
36
  // either/or dispatch). background/highlight/show_only bodies pass through
37
37
  // untouched, same as union/difference/intersection.
38
+ // Vertex positions out of a MeshGL, whatever else it carries per vertex.
39
+ // Only the first three properties are the position; numProp may be larger.
40
+ static void appendVertices(const manifold::MeshGL& mesh, std::vector<manifold::vec3>& out) {
41
+ const size_t stride = mesh.numProp;
42
+ if (stride < 3) return;
43
+ for (size_t i = 0; i + 2 < mesh.vertProperties.size(); i += stride)
44
+ out.push_back({mesh.vertProperties[i], mesh.vertProperties[i + 1],
45
+ mesh.vertProperties[i + 2]});
46
+ }
47
+
38
48
  std::vector<ColoredBody> generateHull(Evaluator&, const CSGParams&, const std::vector<std::unique_ptr<CSGNode>>& children,
39
49
  const oscad::ASTNode&) {
40
50
  const std::vector<ColoredBody> bodies = flattenCsgTree(children);
41
51
  if (bodies.empty()) return {};
42
52
  const RoleSplit split = splitByRole(bodies);
43
53
 
54
+ // An open mesh has no Manifold, but a convex hull only ever needed its
55
+ // POINTS -- closedness is irrelevant to one. BOSL2 depends on this:
56
+ // hull_points(pts, fast=true) hands polyhedron() a deliberately garbage
57
+ // face list purely to carry the vertices into hull(), and
58
+ // spheroid(circum=true, style="icosa") goes through it. Dropping those
59
+ // children left the loose triangles to fall through un-hulled, which is
60
+ // what the reference does not do.
61
+ std::vector<manifold::vec3> loosePoints;
62
+ for (const ColoredBody& c : split.displayOnly) {
63
+ if (c.rawMesh) appendVertices(*c.rawMesh, loosePoints);
64
+ }
65
+
44
66
  std::optional<ColoredBody> hullResult;
45
- if (!split.foreground.empty()) {
67
+ if (!split.foreground.empty() || !loosePoints.empty()) {
46
68
  std::vector<manifold::Manifold> bodies3d;
47
69
  for (const ColoredBody& c : split.foreground) {
48
70
  if (c.body) bodies3d.push_back(*c.body);
49
71
  }
50
- if (!bodies3d.empty()) {
72
+ if (!loosePoints.empty()) {
73
+ // Hulling points and bodies together has to go through the
74
+ // point overload, so the solids contribute their vertices too.
75
+ for (const manifold::Manifold& m : bodies3d)
76
+ appendVertices(m.GetMeshGL(), loosePoints);
77
+ ColoredBody cb;
78
+ cb.body = manifold::Manifold::Hull(loosePoints);
79
+ cb.color = split.foreground.empty() ? split.displayOnly.front().color
80
+ : split.foreground.front().color;
81
+ hullResult = std::move(cb);
82
+ } else if (!bodies3d.empty()) {
51
83
  ColoredBody cb;
52
84
  cb.body = manifold::Manifold::Hull(bodies3d);
53
85
  cb.color = split.foreground.front().color;
@@ -71,7 +103,10 @@ std::vector<ColoredBody> generateHull(Evaluator&, const CSGParams&, const std::v
71
103
  result.insert(result.end(), split.background.begin(), split.background.end());
72
104
  result.insert(result.end(), split.highlight.begin(), split.highlight.end());
73
105
  result.insert(result.end(), split.showOnly.begin(), split.showOnly.end());
74
- result.insert(result.end(), split.displayOnly.begin(), split.displayOnly.end());
106
+ // Only pass the open meshes through if they were NOT hulled; hull()
107
+ // consumes its children, so re-adding them would draw them twice.
108
+ if (loosePoints.empty())
109
+ result.insert(result.end(), split.displayOnly.begin(), split.displayOnly.end());
75
110
  return result;
76
111
  }
77
112
 
@@ -44,7 +44,15 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
44
44
  // re-deriving results that would just be discarded. Mirrors
45
45
  // generate_tree()'s own cache check exactly.
46
46
  std::optional<std::string> key;
47
- if (manifoldCache_ && !node.uncacheable) key = cacheKey(node);
47
+ if (manifoldCache_ && !node.uncacheable) {
48
+ key = cacheKey(node);
49
+ // A key that serialized a function literal is not a pure
50
+ // function of content -- it embeds a raw AST address that the
51
+ // next parse can hand out again. Dropping the key here forces
52
+ // both the get() below and the put() further down to be
53
+ // skipped. See CSGNode::keyHasClosure.
54
+ if (node.keyHasClosure) key.reset();
55
+ }
48
56
  std::optional<std::vector<ColoredBody>> cached = key ? manifoldCache_->get(*key) : std::nullopt;
49
57
  if (cached) {
50
58
  node.bodies = std::move(*cached);
@@ -64,7 +72,13 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
64
72
  std::vector<CSGNode*> childPtrs;
65
73
  childPtrs.reserve(node.children.size());
66
74
  for (const std::unique_ptr<CSGNode>& c : node.children) childPtrs.push_back(c.get());
75
+ // Saved and restored rather than left set, exactly as
76
+ // generateWarnEntry is: a sibling generated afterwards is not
77
+ // inside this hull.
78
+ const bool savedInsideHull = insideHull;
79
+ if (node.isBuiltin && node.kind == "hull") insideHull = true;
67
80
  generateTreeImpl(childPtrs);
81
+ insideHull = savedInsideHull;
68
82
 
69
83
  applyDimensionRules(node);
70
84
 
@@ -93,6 +93,32 @@ std::string canonValue(const Value& v) {
93
93
  v);
94
94
  }
95
95
 
96
+ // True if `v` is a function literal, or a list/object holding one at any
97
+ // depth. Drives CSGNode::keyHasClosure -- see its doc comment for why a
98
+ // closure cannot be keyed.
99
+ bool valueHasClosure(const Value& v) {
100
+ return std::visit(
101
+ [](const auto& val) -> bool {
102
+ using T = std::decay_t<decltype(val)>;
103
+ if constexpr (std::is_same_v<T, ClosurePtr>) {
104
+ return true;
105
+ } else if constexpr (std::is_same_v<T, ListPtr>) {
106
+ if (!val) return false;
107
+ for (const Value& item : val->items)
108
+ if (valueHasClosure(item)) return true;
109
+ return false;
110
+ } else if constexpr (std::is_same_v<T, ObjectPtr>) {
111
+ if (!val) return false;
112
+ for (const auto& [k, vv] : val->items)
113
+ if (valueHasClosure(vv)) return true;
114
+ return false;
115
+ } else {
116
+ return false;
117
+ }
118
+ },
119
+ v);
120
+ }
121
+
96
122
  std::string canonParams(const CSGParams& params) {
97
123
  std::vector<std::pair<std::string, std::string>> entries;
98
124
  entries.reserve(params.size());
@@ -120,13 +146,24 @@ std::string canonParams(const CSGParams& params) {
120
146
  std::string cacheKey(CSGNode& node) {
121
147
  if (node.cachedKey) return *node.cachedKey;
122
148
 
149
+ bool hasClosure = false;
150
+ for (const auto& [k, v] : node.params) {
151
+ if (valueHasClosure(v)) { hasClosure = true; break; }
152
+ }
153
+
123
154
  std::string out = "N(";
124
155
  appendLenPrefixed(out, node.kind);
125
156
  out += node.isBuiltin ? "1" : "0";
126
157
  appendLenPrefixed(out, canonParams(node.params));
127
158
  out += std::to_string(node.children.size()) + "[";
128
- for (const auto& c : node.children) appendLenPrefixed(out, cacheKey(*c));
159
+ for (const auto& c : node.children) {
160
+ appendLenPrefixed(out, cacheKey(*c));
161
+ // A closure anywhere below poisons this key too: it is built from
162
+ // the children's keys.
163
+ hasClosure = hasClosure || c->keyHasClosure;
164
+ }
129
165
  out += "])";
166
+ node.keyHasClosure = hasClosure;
130
167
  node.cachedKey = out;
131
168
  return *node.cachedKey;
132
169
  }
@@ -314,3 +314,81 @@ TEST(WarningTrace, FromClauseNamesOutermostFrameNotImmediateCaller) {
314
314
  EXPECT_NE(warning.find("from <string>, line 3"), std::string::npos) << warning;
315
315
  EXPECT_EQ(warning.find("from <string>, line 2"), std::string::npos) << warning;
316
316
  }
317
+
318
+ // -- hull() over an open mesh ------------------------------------------
319
+ //
320
+ // A convex hull needs only its input's POINTS; whether the mesh that
321
+ // carried them is closed is irrelevant. BOSL2 leans on this hard:
322
+ // hull_points(pts, fast=true) hands polyhedron() a deliberately garbage
323
+ // face list purely as a vehicle for the vertices, and
324
+ // spheroid(circum=true, style="icosa") goes through that path. Treating
325
+ // the open mesh as display-only left the loose triangles to fall out of
326
+ // hull() un-hulled -- the model rendered as a scatter of shards where the
327
+ // reference produced a solid.
328
+
329
+ TEST(HullOpenMesh, HullsThePointsOfAMeshThatDoesNotClose) {
330
+ Evaluated e = evalSrc(
331
+ "pts = [[0,0,0],[10,0,0],[10,10,0],[0,10,0],\n"
332
+ " [0,0,10],[10,0,10],[10,10,10],[0,10,10]];\n"
333
+ "hull() polyhedron(points=pts, faces=[[0,1,2],[3,4,5],[6,7,0]]);\n");
334
+
335
+ ASSERT_EQ(e.bodies.size(), 1u) << "the open mesh must be consumed, not passed through";
336
+ ASSERT_TRUE(e.bodies[0].body.has_value());
337
+ EXPECT_FALSE(e.bodies[0].isDisplayOnly());
338
+ EXPECT_EQ(e.bodies[0].body->Status(), manifold::Manifold::Error::NoError);
339
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 1000.0, 1e-6);
340
+ }
341
+
342
+ TEST(HullOpenMesh, AnOpenMeshAndASolidHullTogether) {
343
+ // The solid's own vertices must survive the switch to the point-based
344
+ // hull: hulling only the loose points would silently shrink the result.
345
+ Evaluated e = evalSrc(
346
+ "hull() {\n"
347
+ " polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0]], faces=[[0,1,2]]);\n"
348
+ " translate([0,0,10]) cube(10);\n"
349
+ "}\n");
350
+
351
+ ASSERT_EQ(e.bodies.size(), 1u);
352
+ ASSERT_TRUE(e.bodies[0].body.has_value());
353
+ EXPECT_EQ(e.bodies[0].body->Status(), manifold::Manifold::Error::NoError);
354
+ // Hull of the z=0 square and the 10..20 cube: a prism, strictly larger
355
+ // than the cube alone.
356
+ EXPECT_GT(e.bodies[0].body->Volume(), 1000.0);
357
+ }
358
+
359
+ TEST(HullOpenMesh, AnOpenMeshOutsideAHullStillPassesThroughUntouched) {
360
+ Evaluated e = evalSrc(
361
+ "polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0]], faces=[[0,1,2]]);\n");
362
+
363
+ ASSERT_EQ(e.bodies.size(), 1u);
364
+ EXPECT_TRUE(e.bodies[0].isDisplayOnly()) << "only hull() may consume it";
365
+ }
366
+
367
+ TEST(HullOpenMesh, NoWarningForAnOpenMeshInsideAHull) {
368
+ // The reference is silent here, and BOSL2 reaches this on an everyday
369
+ // spheroid(circum=true, style="icosa") -- warning would cry wolf.
370
+ std::string warning;
371
+ evalSrc("hull() polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0],\n"
372
+ " [0,0,10],[10,0,10],[10,10,10],[0,10,10]],\n"
373
+ " faces=[[0,1,2],[3,4,5],[6,7,0]]);\n",
374
+ [&](const std::string& m) { if (warning.empty()) warning = m; });
375
+ EXPECT_EQ(warning, "") << warning;
376
+ }
377
+
378
+ TEST(HullOpenMesh, SuppressionCoversTheWholeSubtreeNotJustDirectChildren) {
379
+ std::string warning;
380
+ evalSrc("hull() { translate([0,0,1]) polyhedron(\n"
381
+ " points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0]], faces=[[0,1,2]]); cube(1); }\n",
382
+ [&](const std::string& m) { if (warning.empty()) warning = m; });
383
+ EXPECT_EQ(warning, "") << warning;
384
+ }
385
+
386
+ TEST(HullOpenMesh, ASiblingAfterAHullStillWarns) {
387
+ // The flag is restored on the way out; an open mesh that is NOT hulled
388
+ // is still a real mistake.
389
+ std::string warning;
390
+ evalSrc("hull() cube(1);\n"
391
+ "polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0]], faces=[[0,1,2]]);\n",
392
+ [&](const std::string& m) { if (warning.empty()) warning = m; });
393
+ EXPECT_NE(warning.find("mesh is not closed"), std::string::npos) << warning;
394
+ }
@@ -121,6 +121,65 @@ TEST(ManifoldCacheUncacheable, RandsBeforeGeometryInModuleBodyTaintsSplicedSibli
121
121
  EXPECT_TRUE(e.tree[0]->uncacheable);
122
122
  }
123
123
 
124
+ // -- closure taint ---------------------------------------------------------
125
+
126
+ TEST(ManifoldCacheClosure, PlainNodeHasNoClosureTaint) {
127
+ Evaluated e = evalSrc("cube(5);");
128
+ ASSERT_EQ(e.tree.size(), 1u);
129
+ cacheKey(*e.tree[0]);
130
+ EXPECT_FALSE(e.tree[0]->keyHasClosure);
131
+ }
132
+
133
+ TEST(ManifoldCacheClosure, FunctionLiteralParamTaintsTheNode) {
134
+ Evaluated e = evalSrc(
135
+ "levelset(function (x,y,z) x*x+y*y+z*z-100,"
136
+ " bounds=[[-15,-15,-15],[15,15,15]], edge=5);");
137
+ ASSERT_EQ(e.tree.size(), 1u);
138
+ cacheKey(*e.tree[0]);
139
+ EXPECT_TRUE(e.tree[0]->keyHasClosure);
140
+ }
141
+
142
+ TEST(ManifoldCacheClosure, TaintPropagatesUpThroughATransform) {
143
+ // The parent's key is built from the child's, so a closure below is
144
+ // just as unusable there.
145
+ Evaluated e = evalSrc(
146
+ "translate([1,0,0]) levelset(function (x,y,z) x*x+y*y+z*z-100,"
147
+ " bounds=[[-15,-15,-15],[15,15,15]], edge=5);");
148
+ ASSERT_EQ(e.tree.size(), 1u);
149
+ EXPECT_EQ(e.tree[0]->kind, "translate");
150
+ cacheKey(*e.tree[0]);
151
+ EXPECT_TRUE(e.tree[0]->keyHasClosure);
152
+ }
153
+
154
+ // The bug this taint exists for, at full size: both closures come from the
155
+ // SAME function literal (so canonValue's "F<pointer>" is byte-identical)
156
+ // but capture different `r`. Every other cache-key input matches too, so
157
+ // before the taint the second levelset() was served the first one's
158
+ // geometry and both spheres came out radius 10.
159
+ //
160
+ // Deterministic on purpose: one evaluation, one AST, so the two closures
161
+ // genuinely share a node address rather than relying on the allocator
162
+ // handing back a freed one.
163
+ TEST(ManifoldCacheClosure, TwoClosuresOverOneLiteralAreNotServedEachOthersGeometry) {
164
+ auto cache = std::make_shared<ManifoldCache>();
165
+ Evaluated e = evalSrcWithCache(
166
+ "function mk(r) = function (x,y,z) x*x+y*y+z*z-r*r;\n"
167
+ "levelset(mk(10), bounds=[[-15,-15,-15],[15,15,15]], edge=2);\n"
168
+ "translate([40,0,0])\n"
169
+ " levelset(mk(5), bounds=[[-15,-15,-15],[15,15,15]], edge=2);\n",
170
+ cache);
171
+
172
+ ASSERT_EQ(e.bodies.size(), 2u);
173
+ ASSERT_TRUE(e.bodies[0].body.has_value());
174
+ ASSERT_TRUE(e.bodies[1].body.has_value());
175
+ const double volBig = e.bodies[0].body->Volume();
176
+ const double volSmall = e.bodies[1].body->Volume();
177
+ // Coarse sampling, so compare the two against each other rather than
178
+ // against 4/3*pi*r^3: r=10 vs r=5 is an 8x volume ratio.
179
+ EXPECT_GT(volBig, 2.0 * volSmall)
180
+ << "the second closure was served the first one's cached geometry";
181
+ }
182
+
124
183
  // -- cache reuse -----------------------------------------------------------
125
184
 
126
185
  TEST(ManifoldCache, CacheHitSkipsRegeneratingTheSubtree) {