openscad-cpp-evaluator 1.47.0__tar.gz → 1.48.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 (234) hide show
  1. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/CLAUDE.md +6 -0
  2. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/function_builtins.cpp +1 -1
  5. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/topology.cpp +100 -45
  6. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_booleans.cpp +70 -2
  7. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/uv.lock +1 -1
  8. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/.github/workflows/ci.yml +0 -0
  9. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/.github/workflows/wheels.yml +0 -0
  10. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/.gitignore +0 -0
  11. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/.gitmodules +0 -0
  12. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/CMakeLists.txt +0 -0
  13. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/LICENSE +0 -0
  14. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/README.md +0 -0
  15. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/bindings/CMakeLists.txt +0 -0
  16. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/bindings/ast_to_py.cpp +0 -0
  17. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/bindings/module.cpp +0 -0
  18. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/cmake/embed_font.cmake +0 -0
  19. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/cmake/patch_manifold.cmake +0 -0
  20. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/examples/CMakeLists.txt +0 -0
  21. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/examples/eval_perf_benchmark.cpp +0 -0
  22. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/examples/manifold_cache_reuse.cpp +0 -0
  23. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/examples/minimal_debugger.cpp +0 -0
  24. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  25. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/.gitignore +0 -0
  26. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  27. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/LICENSE +0 -0
  29. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/README.md +0 -0
  30. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  31. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  32. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  33. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  34. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  35. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  36. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  37. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  38. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  39. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  41. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  42. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  43. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  44. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  45. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  46. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  47. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  48. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  49. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  50. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  51. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  52. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  53. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  54. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  55. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  56. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  57. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  58. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  59. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  60. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  61. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  62. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  63. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  64. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  65. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  66. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  67. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  68. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  69. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  70. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  71. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  72. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  73. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  74. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  75. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  76. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  77. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  78. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  79. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  80. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  81. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  82. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  83. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  84. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  85. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  86. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_profile_time.cpp +0 -0
  87. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  88. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  89. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  90. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  91. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  92. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  93. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  94. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  95. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  96. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  97. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  98. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  99. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  100. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  101. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  102. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  103. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  104. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  105. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  106. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  107. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  108. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  109. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  110. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  111. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  112. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  113. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  114. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  115. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  116. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  117. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  118. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  119. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  120. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  121. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  122. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  123. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  124. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  125. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  126. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  127. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  128. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  129. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  130. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  131. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  132. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  133. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  134. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/resources/fonts/LICENSE +0 -0
  135. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  136. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  137. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  138. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  139. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/CMakeLists.txt +0 -0
  140. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/booleans.cpp +0 -0
  141. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/builtins.hpp +0 -0
  142. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/call_args.cpp +0 -0
  143. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/color.cpp +0 -0
  144. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/control.cpp +0 -0
  145. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/dxf_dim.cpp +0 -0
  146. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/extrude.cpp +0 -0
  147. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/import.cpp +0 -0
  148. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/modifiers.cpp +0 -0
  149. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/primitives_2d.cpp +0 -0
  150. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/primitives_3d.cpp +0 -0
  151. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/profile_time.cpp +0 -0
  152. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/registry.cpp +0 -0
  153. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/roof.cpp +0 -0
  154. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/segments.cpp +0 -0
  155. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/surface.cpp +0 -0
  156. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/text.cpp +0 -0
  157. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/transforms.cpp +0 -0
  158. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/bytecode_compiler.cpp +0 -0
  159. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/bytecode_vm.cpp +0 -0
  160. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/colored_body.cpp +0 -0
  161. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/coverage.cpp +0 -0
  162. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/csg_generate.cpp +0 -0
  163. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/csg_resolve.cpp +0 -0
  164. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/css_colors.cpp +0 -0
  165. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/debug/debug_repl.cpp +0 -0
  166. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/debug_profile.cpp +0 -0
  167. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/eval_context.cpp +0 -0
  168. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/eval_error.cpp +0 -0
  169. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/eval_use.cpp +0 -0
  170. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/export.cpp +0 -0
  171. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/expr_eval.cpp +0 -0
  172. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/format_closure.cpp +0 -0
  173. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/import/dxf_import.cpp +0 -0
  174. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/import/mesh_import.cpp +0 -0
  175. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/import/scene_import.cpp +0 -0
  176. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/import/surface_load.cpp +0 -0
  177. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/import/svg_import.cpp +0 -0
  178. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/manifold_cache.cpp +0 -0
  179. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/measure_geometry.cpp +0 -0
  180. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/mesh_check.cpp +0 -0
  181. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/stmt_eval.cpp +0 -0
  182. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/text/font_match.cpp +0 -0
  183. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/text/freetype_font_provider.cpp +0 -0
  184. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/text/text_metrics.cpp +0 -0
  185. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/user_calls.cpp +0 -0
  186. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/utf8.cpp +0 -0
  187. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/value.cpp +0 -0
  188. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/xkcd_colors.cpp +0 -0
  189. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/xkcd_colors.hpp +0 -0
  190. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/src/zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_bytecode_compiler.cpp +0 -0
  193. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_children_separate.cpp +0 -0
  194. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_cli.cpp +0 -0
  195. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_control_flow.cpp +0 -0
  196. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_coverage.cpp +0 -0
  197. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_csg_tree.cpp +0 -0
  198. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_debug_hooks.cpp +0 -0
  199. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_discretizer.cpp +0 -0
  200. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_dxf_svg_import.cpp +0 -0
  201. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_eval_context.cpp +0 -0
  202. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_export.cpp +0 -0
  203. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_expr_eval.cpp +0 -0
  204. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_extrude_roof.cpp +0 -0
  205. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_feature_detection.cpp +0 -0
  206. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_function_builtins.cpp +0 -0
  207. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_helpers.hpp +0 -0
  208. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_import_export.cpp +0 -0
  209. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_include_cache.cpp +0 -0
  210. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_manifold_cache.cpp +0 -0
  211. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_mesh_check.cpp +0 -0
  212. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_multi_color_merge.cpp +0 -0
  213. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_openscad_parity.cpp +0 -0
  214. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_primitives.cpp +0 -0
  215. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_profile_time.cpp +0 -0
  216. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_profiling.cpp +0 -0
  217. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_python_bindings.py +0 -0
  218. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_render_expr.cpp +0 -0
  219. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_scope_trail.cpp +0 -0
  220. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_scoping.cpp +0 -0
  221. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_surface.cpp +0 -0
  222. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_tail_calls.cpp +0 -0
  223. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_text.cpp +0 -0
  224. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_transforms.cpp +0 -0
  225. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_undef_args.cpp +0 -0
  226. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_unexpected_args.cpp +0 -0
  227. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_use.cpp +0 -0
  228. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_value.cpp +0 -0
  229. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_viewport_params.cpp +0 -0
  230. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tests/test_zip_stored.cpp +0 -0
  231. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tools/cli/CMakeLists.txt +0 -0
  232. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tools/cli/cli_lib.cpp +0 -0
  233. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tools/cli/cli_lib.hpp +0 -0
  234. {openscad_cpp_evaluator-1.47.0 → openscad_cpp_evaluator-1.48.0}/tools/cli/main.cpp +0 -0
@@ -606,6 +606,12 @@ grep for `ponytail:`.
606
606
  field squashed to unit spacing and `Scale`s the result back -- exact, since with tolerance -1 a
607
607
  vertex is a linear interpolation along a lattice edge, which commutes with an axis scale. A plain
608
608
  number takes the old path unchanged. `supported_feature("levelset")` is **2** from this change.
609
+ **A field array needs `bounds` OR `edge`**: given only `edge` (number or vector), the samples sit
610
+ `edge` apart **centred on the origin**, as BOSL2's `isosurface()` places an array given
611
+ `voxel_size`, and the array's depth (`field[i][j]` vs `field[i][j][k]`) decides 2D or 3D
612
+ (`boundsFromEdge`, #749 on BelfrySCAD). Both given: `edge` re-samples the field inside `bounds`
613
+ (trilinear in 3D, bilinear in 2D -- 2D used to ignore `edge` here) unless it equals the grid's own spacing. A function field still needs `bounds`.
614
+ `supported_feature("levelset")` is **3** from this change.
609
615
  **The field is a grid OR a `function(x,y,z)`, and the two trade off against each other** —
610
616
  neither is simply better:
611
617
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.47.0
3
+ Version: 1.48.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT AND OFL-1.1
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.47.0"
7
+ version = "1.48.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -987,7 +987,7 @@ const std::unordered_map<std::string, double>& featureLevels() {
987
987
  static const std::unordered_map<std::string, double> levels = {
988
988
  {"render-expr", 1.0}, // render() in expression position
989
989
  {"linear-solve", 1.0}, // linear_solve(A, b) -> {x, det, singular}
990
- {"levelset", 2.0}, // levelset(field, bounds, isovalue); 2: edge may be a vector
990
+ {"levelset", 3.0}, // levelset(field, bounds, isovalue); 2: edge may be a vector; 3: edge alone places a field array
991
991
  {"polyhedron-vnf", 1.0}, // polyhedron(vnf) / polyhedron(object)
992
992
  {"separate-children", 1.0}, // children(..., separate=true)
993
993
  {"minkowski-diff", 1.0}, // minkowski_difference()
@@ -7,6 +7,7 @@
7
7
  #include <manifold/polygon.h>
8
8
 
9
9
  #include <algorithm>
10
+ #include <utility>
10
11
 
11
12
  namespace oscadeval {
12
13
 
@@ -689,6 +690,44 @@ std::optional<double> fieldSample(const Value& item) {
689
690
  return std::clamp(*d, -kHuge, kHuge);
690
691
  }
691
692
 
693
+ // bounds for a field ARRAY given edge= and no bounds (#749 on BelfrySCAD):
694
+ // the samples edge apart, centred on the origin, as BOSL2's isosurface()
695
+ // places an array given voxel_size. The array's depth (field[i][j] or
696
+ // field[i][j][k]) says 2D or 3D; the sizes come from its first row, and
697
+ // readField/readPlane check the rest later. nullopt when edge is missing
698
+ // or does not fit, leaving the caller's "bounds must be ..." warning.
699
+ std::optional<std::pair<std::vector<double>, std::vector<double>>>
700
+ boundsFromEdge(const Value& field, const Value& edge) {
701
+ std::vector<size_t> dims;
702
+ for (const Value* cur = &field; dims.size() < 3;) {
703
+ const ListPtr* l = std::get_if<ListPtr>(cur);
704
+ if (!l || !*l || (*l)->items.empty()) break;
705
+ dims.push_back((*l)->items.size());
706
+ cur = &(*l)->items[0];
707
+ }
708
+ if (dims.size() < 2) return std::nullopt;
709
+ std::vector<double> step;
710
+ if (const double* e = std::get_if<double>(&edge)) {
711
+ step.assign(dims.size(), *e);
712
+ } else if (const ListPtr* l = std::get_if<ListPtr>(&edge); l && *l && (*l)->items.size() == dims.size()) {
713
+ for (const Value& item : (*l)->items) {
714
+ const double* d = std::get_if<double>(&item);
715
+ if (!d) return std::nullopt;
716
+ step.push_back(*d);
717
+ }
718
+ } else {
719
+ return std::nullopt;
720
+ }
721
+ std::vector<double> lo, hi;
722
+ for (size_t a = 0; a < dims.size(); ++a) {
723
+ if (!std::isfinite(step[a]) || step[a] <= 0.0 || dims[a] < 2) return std::nullopt;
724
+ const double half = 0.5 * step[a] * static_cast<double>(dims[a] - 1);
725
+ lo.push_back(-half);
726
+ hi.push_back(half);
727
+ }
728
+ return std::make_pair(lo, hi);
729
+ }
730
+
692
731
  struct ScalarField {
693
732
  std::vector<double> v; // flat, x fastest
694
733
  size_t nx = 0, ny = 0, nz = 0;
@@ -1108,12 +1147,20 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1108
1147
  // matches how the caller already has to think about the box.
1109
1148
  const ListPtr* bb = std::get_if<ListPtr>(&params.at("bounds"));
1110
1149
  std::optional<std::vector<double>> bLo, bHi;
1150
+ const bool noBounds = std::holds_alternative<std::monostate>(params.at("bounds"));
1111
1151
  if (bb && *bb && (*bb)->items.size() == 2) {
1112
1152
  bLo = numbersOrInf((*bb)->items[0]);
1113
1153
  bHi = numbersOrInf((*bb)->items[1]);
1154
+ } else if (noBounds && !fieldFn) {
1155
+ if (const auto inferred = boundsFromEdge(fieldArg, params.at("edge"))) {
1156
+ bLo = inferred->first;
1157
+ bHi = inferred->second;
1158
+ }
1114
1159
  }
1115
1160
  if (!bLo || !bHi || bLo->size() != bHi->size() || (bLo->size() != 2 && bLo->size() != 3)) {
1116
- ev.warn("levelset(): bounds must be [[x0,y0],[x1,y1]] or [[x0,y0,z0],[x1,y1,z1]]",
1161
+ ev.warn(noBounds && !fieldFn
1162
+ ? "levelset(): a field array needs bounds=, or edge= (its sample spacing) to place it"
1163
+ : "levelset(): bounds must be [[x0,y0],[x1,y1]] or [[x0,y0,z0],[x1,y1,z1]]",
1117
1164
  &node.position());
1118
1165
  return {};
1119
1166
  }
@@ -1211,74 +1258,82 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1211
1258
 
1212
1259
  // ---- 2D: sample onto a padded grid, contour it, hand to CrossSection.
1213
1260
  if (is2d) {
1214
- size_t nx = 0, ny = 0;
1215
- double sx = 0, sy = 0;
1216
1261
  if (field) {
1217
1262
  ev.warn("levelset(): a 2D field must be field[i][j]; a 3D array was given",
1218
1263
  &node.position());
1219
1264
  return {};
1220
1265
  }
1266
+ const double x0 = (*bLo)[0], y0 = (*bLo)[1];
1267
+ const double wx = (*bHi)[0] - x0, wy = (*bHi)[1] - y0;
1268
+ double ex = edgeVec ? edgeV[0] : edge, ey = edgeVec ? edgeV[1] : edge;
1269
+ std::optional<std::vector<std::vector<double>>> plane;
1270
+ size_t px = 0, py = 0;
1271
+ bool ownSamples = false;
1221
1272
  if (!fieldFn) {
1222
- const std::optional<std::vector<std::vector<double>>> plane = readPlane(fieldArg);
1273
+ plane = readPlane(fieldArg);
1223
1274
  if (!plane || plane->size() < 2 || (*plane)[0].size() < 2) {
1224
1275
  ev.warn("levelset(): 2D field must be a rectangular field[i][j] of numbers",
1225
1276
  &node.position());
1226
1277
  return {};
1227
1278
  }
1228
- nx = plane->size();
1229
- ny = (*plane)[0].size();
1230
- sx = ((*bHi)[0] - (*bLo)[0]) / static_cast<double>(nx - 1);
1231
- sy = ((*bHi)[1] - (*bLo)[1]) / static_cast<double>(ny - 1);
1232
- Contours2d g;
1233
- g.nx = nx + 2;
1234
- g.ny = ny + 2;
1235
- g.dx = sx;
1236
- g.dy = sy;
1237
- g.ox = (*bLo)[0] - sx;
1238
- g.oy = (*bLo)[1] - sy;
1239
- // Padded with a ring that is firmly OUTSIDE, so a contour meeting
1240
- // the box edge closes along it instead of running off. This is
1241
- // BOSL2's closed=true, and the only behaviour offered.
1242
- g.v.assign(g.nx * g.ny, -1.0);
1243
- for (size_t i = 0; i < nx; ++i)
1244
- for (size_t j = 0; j < ny; ++j)
1245
- g.v[(j + 1) * g.nx + (i + 1)] = bandDistance((*plane)[i][j], isoLo, isoHi, invert);
1246
- const manifold::Polygons polys = marchingSquares(g);
1247
- if (polys.empty()) return {};
1248
- ColoredBody b;
1249
- b.section = clipToBounds(manifold::CrossSection(polys, manifold::CrossSection::FillRule::EvenOdd),
1250
- *bLo, *bHi);
1251
- if (b.section->IsEmpty()) return {};
1252
- std::vector<ColoredBody> out;
1253
- out.push_back(std::move(b));
1254
- return out;
1279
+ px = plane->size();
1280
+ py = (*plane)[0].size();
1281
+ // Contoured on its own samples unless edge= asks for another
1282
+ // spacing, as the 3D grid is marched; then re-sampled bilinearly.
1283
+ const double gx = wx / static_cast<double>(px - 1), gy = wy / static_cast<double>(py - 1);
1284
+ const auto same = [](double a, double b) { return std::abs(a - b) <= 1e-9 * std::max(a, b); };
1285
+ ownSamples = ex <= 0.0 || (same(ex, gx) && same(ey, gy));
1255
1286
  }
1256
- // function(x,y): edge= gives the spacing, as in 3D
1257
- const double ex = edgeVec ? edgeV[0] : edge, ey = edgeVec ? edgeV[1] : edge;
1258
- nx = static_cast<size_t>(std::floor(((*bHi)[0] - (*bLo)[0]) / ex)) + 1;
1259
- ny = static_cast<size_t>(std::floor(((*bHi)[1] - (*bLo)[1]) / ey)) + 1;
1287
+ // Counted from the array, not floor(width / spacing): rounding there
1288
+ // could drop the last sample row.
1289
+ const size_t nx = ownSamples ? px : static_cast<size_t>(std::floor(wx / ex)) + 1;
1290
+ const size_t ny = ownSamples ? py : static_cast<size_t>(std::floor(wy / ey)) + 1;
1260
1291
  if (nx < 2 || ny < 2) {
1261
1292
  ev.warn("levelset(): edge is larger than the bounds", &node.position());
1262
1293
  return {};
1263
1294
  }
1264
- sx = ((*bHi)[0] - (*bLo)[0]) / static_cast<double>(nx - 1);
1265
- sy = ((*bHi)[1] - (*bLo)[1]) / static_cast<double>(ny - 1);
1295
+ const double sx = wx / static_cast<double>(nx - 1), sy = wy / static_cast<double>(ny - 1);
1296
+ // The plane at a point between its samples. Clamped at the edges,
1297
+ // like the 3D grid sampler, so the clip below cuts a clean line.
1298
+ const auto bilinear = [&](double x, double y) {
1299
+ const auto cell = [](double u, size_t n, size_t& i0) {
1300
+ i0 = static_cast<size_t>(std::clamp(std::floor(u), 0.0, static_cast<double>(n - 2)));
1301
+ return std::clamp(u - static_cast<double>(i0), 0.0, 1.0);
1302
+ };
1303
+ size_t i, j;
1304
+ const double tx = cell((x - x0) / wx * static_cast<double>(px - 1), px, i);
1305
+ const double ty = cell((y - y0) / wy * static_cast<double>(py - 1), py, j);
1306
+ const auto& p = *plane;
1307
+ return (1 - tx) * ((1 - ty) * p[i][j] + ty * p[i][j + 1])
1308
+ + tx * ((1 - ty) * p[i + 1][j] + ty * p[i + 1][j + 1]);
1309
+ };
1266
1310
  Contours2d g;
1267
1311
  g.nx = nx + 2;
1268
1312
  g.ny = ny + 2;
1269
1313
  g.dx = sx;
1270
1314
  g.dy = sy;
1271
- g.ox = (*bLo)[0] - sx;
1272
- g.oy = (*bLo)[1] - sy;
1315
+ g.ox = x0 - sx;
1316
+ g.oy = y0 - sy;
1317
+ // Padded with a ring that is firmly OUTSIDE, so a contour meeting
1318
+ // the box edge closes along it instead of running off. This is
1319
+ // BOSL2's closed=true, and the only behaviour offered.
1273
1320
  g.v.assign(g.nx * g.ny, -1.0);
1274
1321
  for (size_t i = 0; i < nx; ++i) {
1275
1322
  for (size_t j = 0; j < ny; ++j) {
1276
- BoundArgs bound;
1277
- bound.set(fnParams2[0], Value{(*bLo)[0] + static_cast<double>(i) * sx});
1278
- bound.set(fnParams2[1], Value{(*bLo)[1] + static_cast<double>(j) * sy});
1279
- const Value outv =
1280
- ev.evalFunctionLiteralFromBound(**fieldFn, std::move(bound), *fnCtx, &node.position());
1281
- const double val = fieldSample(outv).value_or(std::numeric_limits<double>::max());
1323
+ const double x = x0 + static_cast<double>(i) * sx, y = y0 + static_cast<double>(j) * sy;
1324
+ double val;
1325
+ if (ownSamples) {
1326
+ val = (*plane)[i][j];
1327
+ } else if (plane) {
1328
+ val = bilinear(x, y);
1329
+ } else {
1330
+ BoundArgs bound;
1331
+ bound.set(fnParams2[0], Value{x});
1332
+ bound.set(fnParams2[1], Value{y});
1333
+ const Value outv =
1334
+ ev.evalFunctionLiteralFromBound(**fieldFn, std::move(bound), *fnCtx, &node.position());
1335
+ val = fieldSample(outv).value_or(std::numeric_limits<double>::max());
1336
+ }
1282
1337
  g.v[(j + 1) * g.nx + (i + 1)] = bandDistance(val, isoLo, isoHi, invert);
1283
1338
  }
1284
1339
  }
@@ -1374,11 +1374,12 @@ TEST(LevelSetFn, AVectorEdgeOfTheWrongSizeWarns) {
1374
1374
 
1375
1375
  TEST(LevelSetFn, SupportedFeatureSaysAVectorEdgeWorks) {
1376
1376
  // Level 2 is how a library such as BOSL2 tells a build with per-axis
1377
- // edge= from one that would reject it.
1377
+ // edge= from one that would reject it; level 3, one that places a
1378
+ // field array from edge= alone (#749 on BelfrySCAD).
1378
1379
  std::vector<std::string> out;
1379
1380
  evalSrc("echo(supported_feature(\"levelset\"));", [&](const std::string& m) { out.push_back(m); });
1380
1381
  ASSERT_EQ(out.size(), 1u);
1381
- EXPECT_NE(out[0].find("2"), std::string::npos) << out[0];
1382
+ EXPECT_NE(out[0].find("3"), std::string::npos) << out[0];
1382
1383
  }
1383
1384
 
1384
1385
  TEST(LevelSetFn, AFunctionOfTheWrongArityWarns) {
@@ -1674,6 +1675,73 @@ TEST(LevelSetGrid, TheDefaultMarchesTheGridItself) {
1674
1675
  EXPECT_EQ(soleBody(same).NumTri(), soleBody(e).NumTri());
1675
1676
  }
1676
1677
 
1678
+ TEST(LevelSetGrid, EdgeWithoutBoundsCentresTheGridOnTheOrigin) {
1679
+ // #749 on BelfrySCAD: edge= alone places a field array, its samples
1680
+ // edge apart and centred on the origin, as BOSL2's isosurface() places
1681
+ // an array given voxel_size. Same mesh as the explicit bounds.
1682
+ Evaluated e = evalSrc(sphereFieldSrc(41, 20) + "levelset(f, isovalue=15, edge=1);");
1683
+ Evaluated ref = evalSrc(sphereFieldSrc(41, 20) +
1684
+ "levelset(f, bounds=[[-20,-20,-20],[20,20,20]], isovalue=15);");
1685
+ ASSERT_EQ(e.bodies.size(), 1u);
1686
+ ASSERT_EQ(ref.bodies.size(), 1u);
1687
+ EXPECT_EQ(soleBody(e).NumTri(), soleBody(ref).NumTri());
1688
+ EXPECT_NEAR(soleBody(e).Volume(), soleBody(ref).Volume(), 1e-9 * soleBody(ref).Volume());
1689
+ EXPECT_GT(onGridLines(soleBody(e), manifold::vec3(-20.0), manifold::vec3(1.0)), 0.5);
1690
+ }
1691
+
1692
+ TEST(LevelSetGrid, AVectorEdgeWithoutBoundsSpacesEachAxis) {
1693
+ // Every sample inside, so the solid is the whole box: 5 x 3 x 4 samples
1694
+ // at [1, 2, 0.5] span 4 x 4 x 1.5, centred.
1695
+ Evaluated e = evalSrc("f = [for (i=[0:4]) [for (j=[0:2]) [for (k=[0:3]) -1 ]]];\n"
1696
+ "levelset(f, edge=[1, 2, 0.5]);");
1697
+ ASSERT_EQ(e.bodies.size(), 1u);
1698
+ const manifold::Box b = soleBody(e).BoundingBox();
1699
+ EXPECT_NEAR(b.min.x, -2.0, 1e-9); EXPECT_NEAR(b.max.x, 2.0, 1e-9);
1700
+ EXPECT_NEAR(b.min.y, -2.0, 1e-9); EXPECT_NEAR(b.max.y, 2.0, 1e-9);
1701
+ EXPECT_NEAR(b.min.z, -0.75, 1e-9); EXPECT_NEAR(b.max.z, 0.75, 1e-9);
1702
+ }
1703
+
1704
+ TEST(LevelSetGrid, A2DFieldArrayIsPlacedByEdgeToo) {
1705
+ // field[i][j] says 2D without a bounds to say so.
1706
+ Evaluated e = evalSrc("f = [for (i=[0:4]) [for (j=[0:2]) -1 ]];\nlevelset(f, edge=2);");
1707
+ ASSERT_EQ(e.bodies.size(), 1u);
1708
+ ASSERT_TRUE(e.bodies[0].section.has_value());
1709
+ const manifold::Rect r = e.bodies[0].section->Bounds();
1710
+ EXPECT_NEAR(r.min.x, -4.0, 1e-9); EXPECT_NEAR(r.max.x, 4.0, 1e-9);
1711
+ EXPECT_NEAR(r.min.y, -2.0, 1e-9); EXPECT_NEAR(r.max.y, 2.0, 1e-9);
1712
+ }
1713
+
1714
+ TEST(LevelSetGrid, A2DGridIsResampledAtADifferentEdge) {
1715
+ // As in 3D: edge= beside bounds re-samples the plane (bilinearly), where
1716
+ // it used to be ignored. A cone sampled every 2, contoured at r=7.
1717
+ const std::string field =
1718
+ "f = [for (i=[0:10]) [for (j=[0:10]) let(x=-10+2*i, y=-10+2*j) sqrt(x*x+y*y) ]];\n";
1719
+ const std::string box = "bounds=[[-10,-10],[10,10]], isovalue=7";
1720
+ Evaluated own = evalSrc(field + "levelset(f, " + box + ");");
1721
+ Evaluated same = evalSrc(field + "levelset(f, " + box + ", edge=2);");
1722
+ Evaluated fine = evalSrc(field + "levelset(f, " + box + ", edge=0.25);");
1723
+ for (Evaluated* e : {&own, &same, &fine}) {
1724
+ ASSERT_EQ(e->bodies.size(), 1u);
1725
+ ASSERT_TRUE(e->bodies[0].section.has_value());
1726
+ }
1727
+ EXPECT_EQ(same.bodies[0].section->NumVert(), own.bodies[0].section->NumVert());
1728
+ EXPECT_GT(fine.bodies[0].section->NumVert(), 4 * own.bodies[0].section->NumVert());
1729
+ const double disc = 3.14159265358979 * 49;
1730
+ EXPECT_NEAR(fine.bodies[0].section->Area(), disc, 0.03 * disc);
1731
+ }
1732
+
1733
+ TEST(LevelSetGrid, NeitherBoundsNorEdgeSaysWhatIsMissing) {
1734
+ const std::vector<std::string> w =
1735
+ levelsetWarnings("f = [for (i=[0:4]) [for (j=[0:2]) [for (k=[0:3]) -1 ]]];\nlevelset(f);");
1736
+ ASSERT_EQ(w.size(), 1u);
1737
+ EXPECT_NE(w[0].find("bounds=, or edge="), std::string::npos) << w[0];
1738
+ // A function field has no sizes to infer from: bounds stays required.
1739
+ const std::vector<std::string> fw =
1740
+ levelsetWarnings("levelset(function(x,y,z) x, edge=1);");
1741
+ ASSERT_EQ(fw.size(), 1u);
1742
+ EXPECT_NE(fw[0].find("bounds must be"), std::string::npos) << fw[0];
1743
+ }
1744
+
1677
1745
  TEST(LevelSetGrid, ADifferentEdgeStillResamples) {
1678
1746
  Evaluated e = evalSrc(sphereFieldSrc(41, 20) +
1679
1747
  "levelset(f, bounds=[[-20,-20,-20],[20,20,20]], isovalue=15, edge=0.7);");
@@ -55,7 +55,7 @@ wheels = [
55
55
 
56
56
  [[package]]
57
57
  name = "openscad-cpp-evaluator"
58
- version = "1.47.0"
58
+ version = "1.48.0"
59
59
  source = { editable = "." }
60
60
  dependencies = [
61
61
  { name = "numpy" },