openscad-cpp-evaluator 1.46.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.46.0 → openscad_cpp_evaluator-1.48.0}/CLAUDE.md +6 -0
  2. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/function_builtins.cpp +1 -1
  5. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/topology.cpp +333 -84
  6. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_booleans.cpp +278 -10
  7. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/uv.lock +1 -1
  8. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/.github/workflows/ci.yml +0 -0
  9. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/.github/workflows/wheels.yml +0 -0
  10. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/.gitignore +0 -0
  11. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/.gitmodules +0 -0
  12. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/CMakeLists.txt +0 -0
  13. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/LICENSE +0 -0
  14. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/README.md +0 -0
  15. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/bindings/CMakeLists.txt +0 -0
  16. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/bindings/ast_to_py.cpp +0 -0
  17. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/bindings/module.cpp +0 -0
  18. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/cmake/embed_font.cmake +0 -0
  19. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/cmake/patch_manifold.cmake +0 -0
  20. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/examples/CMakeLists.txt +0 -0
  21. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/examples/eval_perf_benchmark.cpp +0 -0
  22. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/examples/manifold_cache_reuse.cpp +0 -0
  23. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/examples/minimal_debugger.cpp +0 -0
  24. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  25. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/.gitignore +0 -0
  26. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  27. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/LICENSE +0 -0
  29. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/README.md +0 -0
  30. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  31. {openscad_cpp_evaluator-1.46.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.46.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.46.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.46.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.46.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.46.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.46.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.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  39. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  41. {openscad_cpp_evaluator-1.46.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.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  43. {openscad_cpp_evaluator-1.46.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.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  45. {openscad_cpp_evaluator-1.46.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.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  47. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  48. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  49. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  50. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  51. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  52. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  53. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  54. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  55. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  56. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  57. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  58. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  59. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  60. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  61. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  62. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  63. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  64. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  65. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  66. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  67. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  68. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  69. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  70. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  71. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  72. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  73. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  74. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  75. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  76. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  77. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  78. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  79. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  80. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  81. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  82. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  83. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  84. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  85. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  86. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_profile_time.cpp +0 -0
  87. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  88. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  89. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  90. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  91. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  92. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  93. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  94. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  95. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  96. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  97. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  98. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  99. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  100. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  101. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  102. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  103. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  104. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  105. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  106. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  107. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  108. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  109. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  110. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  111. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  112. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  113. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  114. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  115. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  116. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  117. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  118. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  119. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  120. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  121. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  122. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  123. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  124. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  125. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  126. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  127. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  128. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  129. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  130. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  131. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  132. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  133. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  134. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/resources/fonts/LICENSE +0 -0
  135. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  136. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  137. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  138. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  139. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/CMakeLists.txt +0 -0
  140. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/booleans.cpp +0 -0
  141. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/builtins.hpp +0 -0
  142. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/call_args.cpp +0 -0
  143. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/color.cpp +0 -0
  144. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/control.cpp +0 -0
  145. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/dxf_dim.cpp +0 -0
  146. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/extrude.cpp +0 -0
  147. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/import.cpp +0 -0
  148. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/modifiers.cpp +0 -0
  149. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/primitives_2d.cpp +0 -0
  150. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/primitives_3d.cpp +0 -0
  151. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/profile_time.cpp +0 -0
  152. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/registry.cpp +0 -0
  153. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/roof.cpp +0 -0
  154. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/segments.cpp +0 -0
  155. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/surface.cpp +0 -0
  156. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/text.cpp +0 -0
  157. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/builtins/transforms.cpp +0 -0
  158. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/bytecode_compiler.cpp +0 -0
  159. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/bytecode_vm.cpp +0 -0
  160. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/colored_body.cpp +0 -0
  161. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/coverage.cpp +0 -0
  162. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/csg_generate.cpp +0 -0
  163. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/csg_resolve.cpp +0 -0
  164. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/css_colors.cpp +0 -0
  165. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/debug/debug_repl.cpp +0 -0
  166. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/debug_profile.cpp +0 -0
  167. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/eval_context.cpp +0 -0
  168. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/eval_error.cpp +0 -0
  169. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/eval_use.cpp +0 -0
  170. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/export.cpp +0 -0
  171. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/expr_eval.cpp +0 -0
  172. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/format_closure.cpp +0 -0
  173. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/import/dxf_import.cpp +0 -0
  174. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/import/mesh_import.cpp +0 -0
  175. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/import/scene_import.cpp +0 -0
  176. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/import/surface_load.cpp +0 -0
  177. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/import/svg_import.cpp +0 -0
  178. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/manifold_cache.cpp +0 -0
  179. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/measure_geometry.cpp +0 -0
  180. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/mesh_check.cpp +0 -0
  181. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/stmt_eval.cpp +0 -0
  182. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/text/font_match.cpp +0 -0
  183. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/text/freetype_font_provider.cpp +0 -0
  184. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/text/text_metrics.cpp +0 -0
  185. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/user_calls.cpp +0 -0
  186. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/utf8.cpp +0 -0
  187. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/value.cpp +0 -0
  188. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/xkcd_colors.cpp +0 -0
  189. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/xkcd_colors.hpp +0 -0
  190. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/src/zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_bytecode_compiler.cpp +0 -0
  193. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_children_separate.cpp +0 -0
  194. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_cli.cpp +0 -0
  195. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_control_flow.cpp +0 -0
  196. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_coverage.cpp +0 -0
  197. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_csg_tree.cpp +0 -0
  198. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_debug_hooks.cpp +0 -0
  199. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_discretizer.cpp +0 -0
  200. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_dxf_svg_import.cpp +0 -0
  201. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_eval_context.cpp +0 -0
  202. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_export.cpp +0 -0
  203. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_expr_eval.cpp +0 -0
  204. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_extrude_roof.cpp +0 -0
  205. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_feature_detection.cpp +0 -0
  206. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_function_builtins.cpp +0 -0
  207. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_helpers.hpp +0 -0
  208. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_import_export.cpp +0 -0
  209. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_include_cache.cpp +0 -0
  210. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_manifold_cache.cpp +0 -0
  211. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_mesh_check.cpp +0 -0
  212. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_multi_color_merge.cpp +0 -0
  213. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_openscad_parity.cpp +0 -0
  214. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_primitives.cpp +0 -0
  215. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_profile_time.cpp +0 -0
  216. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_profiling.cpp +0 -0
  217. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_python_bindings.py +0 -0
  218. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_render_expr.cpp +0 -0
  219. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_scope_trail.cpp +0 -0
  220. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_scoping.cpp +0 -0
  221. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_surface.cpp +0 -0
  222. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_tail_calls.cpp +0 -0
  223. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_text.cpp +0 -0
  224. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_transforms.cpp +0 -0
  225. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_undef_args.cpp +0 -0
  226. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_unexpected_args.cpp +0 -0
  227. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_use.cpp +0 -0
  228. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_value.cpp +0 -0
  229. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_viewport_params.cpp +0 -0
  230. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tests/test_zip_stored.cpp +0 -0
  231. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tools/cli/CMakeLists.txt +0 -0
  232. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tools/cli/cli_lib.cpp +0 -0
  233. {openscad_cpp_evaluator-1.46.0 → openscad_cpp_evaluator-1.48.0}/tools/cli/cli_lib.hpp +0 -0
  234. {openscad_cpp_evaluator-1.46.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.46.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.46.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()
@@ -6,6 +6,9 @@
6
6
 
7
7
  #include <manifold/polygon.h>
8
8
 
9
+ #include <algorithm>
10
+ #include <utility>
11
+
9
12
  namespace oscadeval {
10
13
 
11
14
  // simplify()'s default tolerance, as a fraction of the body's bounding-box
@@ -663,14 +666,68 @@ std::vector<ColoredBody> generateMeshRepair(Evaluator& ev, const CSGParams& para
663
666
  // The trade is memory: ~67 bytes per cell, so 200^3 is about 550 MB and that
664
667
  // is the practical ceiling. A callback samples lazily and has no ceiling.
665
668
  //
666
- // Accuracy is bounded by the GRID, not by Manifold. Manifold samples on a
667
- // body-centred cubic lattice (two interleaved cubic grids, sdf.cpp:440-446)
668
- // and a plain cubic grid does not line up with those points, so the lambda
669
- // interpolates. Same accuracy as a grid-based marching-cubes implementation
670
- // in script; worse than LevelSet given a true SDF. Do not imply otherwise.
669
+ // Accuracy is bounded by the GRID, not by Manifold. At the grid's own
670
+ // spacing (the default) the grid is marched directly, as BOSL2's
671
+ // isosurface() does -- see marchingCubes(). An edge= asking for a different
672
+ // spacing resamples it through LevelSet instead, which samples a
673
+ // body-centred cubic lattice (sdf.cpp:440-446) that a plain cubic grid does
674
+ // not line up with, so the lambda interpolates trilinearly between samples.
671
675
 
672
676
  namespace {
673
677
 
678
+ // A sample as the field arrays and field functions hand it over. +-INF is a
679
+ // legal sample -- a metaball field is 1/0 wherever a grid point lands on a
680
+ // centre (#744 on BelfrySCAD) -- but it cannot be used as is: the trilinear
681
+ // sampler weights corners, and 0 * INF is NaN. So it becomes a huge FINITE
682
+ // value, which is the limit BOSL2's isosurface() takes (an edge to an INF
683
+ // corner crosses at the other end). A quarter of DBL_MAX so that sums of
684
+ // weighted corners and bandDistance's subtraction cannot overflow. NaN, or
685
+ // anything not a number, has no meaning and is nullopt.
686
+ std::optional<double> fieldSample(const Value& item) {
687
+ const double* d = std::get_if<double>(&item);
688
+ if (!d || std::isnan(*d)) return std::nullopt;
689
+ constexpr double kHuge = std::numeric_limits<double>::max() / 4;
690
+ return std::clamp(*d, -kHuge, kHuge);
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
+
674
731
  struct ScalarField {
675
732
  std::vector<double> v; // flat, x fastest
676
733
  size_t nx = 0, ny = 0, nz = 0;
@@ -698,8 +755,8 @@ std::optional<ScalarField> readField(const Value& val) {
698
755
  return std::nullopt;
699
756
  }
700
757
  for (size_t k = 0; k < f.nz; ++k) {
701
- const double* d = std::get_if<double>(&(*zs)->items[k]);
702
- if (!d || !std::isfinite(*d)) return std::nullopt;
758
+ const std::optional<double> d = fieldSample((*zs)->items[k]);
759
+ if (!d) return std::nullopt;
703
760
  f.v[(k * f.ny + j) * f.nx + i] = *d;
704
761
  }
705
762
  }
@@ -722,8 +779,8 @@ std::optional<std::vector<std::vector<double>>> readPlane(const Value& val) {
722
779
  std::vector<double> row;
723
780
  row.reserve(ny);
724
781
  for (const Value& item : (*ys)->items) {
725
- const double* d = std::get_if<double>(&item);
726
- if (!d || !std::isfinite(*d)) return std::nullopt;
782
+ const std::optional<double> d = fieldSample(item);
783
+ if (!d) return std::nullopt;
727
784
  row.push_back(*d);
728
785
  }
729
786
  out.push_back(std::move(row));
@@ -890,6 +947,162 @@ manifold::Polygons marchingSquares(const Contours2d& g) {
890
947
  }
891
948
 
892
949
 
950
+ // -- 3D: marching cubes on the grid itself -----------------------------------
951
+ //
952
+ // For a grid sampled at the spacing it was given, Manifold's LevelSet is the
953
+ // wrong tool: it samples its own body-centred lattice, which does not line up
954
+ // with the grid, so it meshes a trilinear blend of the data rather than the
955
+ // data. Measured on #745 on BelfrySCAD, that left a metaball field visibly
956
+ // more faceted than BOSL2's isosurface(), which marches the grid itself. So
957
+ // this does too.
958
+ //
959
+ // No 256-case table. The table's hard part is the ambiguous face (two
960
+ // inside corners on a diagonal), and a table that resolves it per CELL can
961
+ // disagree with the neighbouring cell and leave a crack -- fatal here, since
962
+ // Manifold needs a closed mesh. So the triangles are built per FACE instead:
963
+ // each face's crossings are paired into segments by a rule that reads only
964
+ // that face's four values (the face-centre average decides a saddle, as the
965
+ // 2D path does), so the two cells sharing a face always agree. A cell's
966
+ // segments then link into closed loops, one polygon each. Vertices are keyed
967
+ // on GRID EDGE identity, so neighbours share them by construction, exactly as
968
+ // marchingSquares keys on edges rather than coordinates.
969
+ //
970
+ // Orientation: walking a face's corners anticlockwise as seen from outside
971
+ // the cell, a segment runs from an outside->inside crossing to an
972
+ // inside->outside one. A loop of such segments, read in order, is a polygon
973
+ // whose normal points from inside to outside -- Manifold's convention, with
974
+ // inside the positive side.
975
+ //
976
+ // The field arrives as band distance (positive inside) with a ring of
977
+ // "outside" round it, like the 2D path, so every surface closes; the caller
978
+ // clips the overhang back to the bounds.
979
+ manifold::MeshGL64 marchingCubes(const std::vector<double>& d, const size_t n[3],
980
+ const std::array<double, 3>& org, const std::array<double, 3>& h) {
981
+ const auto P = [&](size_t i, size_t j, size_t k) { return (k * n[1] + j) * n[0] + i; };
982
+ // corner c of a cell is offset (c&1, c>>1&1, c>>2&1). Each face's
983
+ // corners, anticlockwise as seen from outside.
984
+ static constexpr int kFaces[6][4] = {
985
+ {0, 2, 3, 1}, {4, 5, 7, 6}, // -z, +z
986
+ {0, 4, 6, 2}, {1, 3, 7, 5}, // -x, +x
987
+ {0, 1, 5, 4}, {2, 6, 7, 3}, // -y, +y
988
+ };
989
+ manifold::MeshGL64 mesh;
990
+ mesh.numProp = 3;
991
+ // ponytail: one int per grid edge, ~12 bytes a sample next to the
992
+ // field's own ~67; key the vertices per slab if this ever binds.
993
+ std::vector<int64_t> edgeVert(3 * n[0] * n[1] * n[2], -1);
994
+ const auto addVert = [&](double x, double y, double z) {
995
+ mesh.vertProperties.insert(mesh.vertProperties.end(), {x, y, z});
996
+ return static_cast<int64_t>(mesh.vertProperties.size() / 3 - 1);
997
+ };
998
+ for (size_t k = 0; k + 1 < n[2]; ++k) {
999
+ for (size_t j = 0; j + 1 < n[1]; ++j) {
1000
+ for (size_t i = 0; i + 1 < n[0]; ++i) {
1001
+ double v[8];
1002
+ size_t p[8];
1003
+ int inside = 0;
1004
+ for (int c = 0; c < 8; ++c) {
1005
+ p[c] = P(i + (c & 1), j + (c >> 1 & 1), k + (c >> 2 & 1));
1006
+ v[c] = d[p[c]];
1007
+ inside += v[c] > 0.0;
1008
+ }
1009
+ if (inside == 0 || inside == 8) continue;
1010
+ // The vertex on the edge between corners a and b.
1011
+ const auto vert = [&](int a, int b) -> int64_t {
1012
+ if (a > b) std::swap(a, b); // a is the lower end
1013
+ const int axis = (b - a) == 1 ? 0 : (b - a) == 2 ? 1 : 2;
1014
+ int64_t& id = edgeVert[3 * p[a] + static_cast<size_t>(axis)];
1015
+ if (id < 0) {
1016
+ const double t = v[a] / (v[a] - v[b]);
1017
+ const double x = org[0] + h[0] * (static_cast<double>(i + (a & 1)) + (axis == 0 ? t : 0.0));
1018
+ const double y = org[1] + h[1] * (static_cast<double>(j + (a >> 1 & 1)) + (axis == 1 ? t : 0.0));
1019
+ const double z = org[2] + h[2] * (static_cast<double>(k + (a >> 2 & 1)) + (axis == 2 ? t : 0.0));
1020
+ id = addVert(x, y, z);
1021
+ }
1022
+ return id;
1023
+ };
1024
+ // Segments start->end, at most two per face.
1025
+ int64_t segFrom[12], segTo[12];
1026
+ int nSeg = 0;
1027
+ for (const auto& f : kFaces) {
1028
+ int64_t cross[4];
1029
+ bool outIn[4];
1030
+ int nc = 0;
1031
+ for (int m = 0; m < 4; ++m) {
1032
+ const int a = f[m], b = f[(m + 1) % 4];
1033
+ if ((v[a] > 0.0) != (v[b] > 0.0)) {
1034
+ cross[nc] = vert(a, b);
1035
+ outIn[nc++] = v[b] > 0.0;
1036
+ }
1037
+ }
1038
+ if (nc == 0) continue;
1039
+ // A saddle joins its inside corners when the face centre
1040
+ // is inside: each out->in crossing then pairs with the
1041
+ // PREVIOUS crossing instead of the next.
1042
+ const bool joined =
1043
+ nc == 4 && v[f[0]] + v[f[1]] + v[f[2]] + v[f[3]] > 0.0;
1044
+ for (int m = 0; m < nc; ++m) {
1045
+ if (!outIn[m]) continue;
1046
+ segFrom[nSeg] = cross[m];
1047
+ segTo[nSeg++] = cross[(m + (joined ? nc - 1 : 1)) % nc];
1048
+ }
1049
+ }
1050
+ // Link into loops; each vertex starts exactly one segment.
1051
+ bool used[12] = {};
1052
+ for (int s0 = 0; s0 < nSeg; ++s0) {
1053
+ if (used[s0]) continue;
1054
+ int64_t loop[12];
1055
+ int len = 0;
1056
+ for (int s = s0; s >= 0 && !used[s];) {
1057
+ used[s] = true;
1058
+ loop[len++] = segFrom[s];
1059
+ const int64_t want = segTo[s];
1060
+ s = -1;
1061
+ for (int q = 0; q < nSeg; ++q)
1062
+ if (!used[q] && segFrom[q] == want) { s = q; break; }
1063
+ }
1064
+ const auto tri = [&](int64_t a, int64_t b, int64_t c) {
1065
+ mesh.triVerts.insert(mesh.triVerts.end(), {static_cast<uint64_t>(a), static_cast<uint64_t>(b),
1066
+ static_cast<uint64_t>(c)});
1067
+ };
1068
+ const auto at = [&](int64_t id, int axis) {
1069
+ return mesh.vertProperties[static_cast<size_t>(id) * 3 + static_cast<size_t>(axis)];
1070
+ };
1071
+ const auto dist2 = [&](int64_t a, int64_t b) {
1072
+ double s2 = 0;
1073
+ for (int ax = 0; ax < 3; ++ax) s2 += (at(a, ax) - at(b, ax)) * (at(a, ax) - at(b, ax));
1074
+ return s2;
1075
+ };
1076
+ if (len == 3) {
1077
+ tri(loop[0], loop[1], loop[2]);
1078
+ } else if (len == 4) {
1079
+ // Split along the shorter diagonal.
1080
+ if (dist2(loop[0], loop[2]) <= dist2(loop[1], loop[3])) {
1081
+ tri(loop[0], loop[1], loop[2]);
1082
+ tri(loop[0], loop[2], loop[3]);
1083
+ } else {
1084
+ tri(loop[1], loop[2], loop[3]);
1085
+ tri(loop[1], loop[3], loop[0]);
1086
+ }
1087
+ } else if (len > 4) {
1088
+ // A fan from one corner of a 5-7 sided, non-planar
1089
+ // loop can fold; a fan from its centroid cannot.
1090
+ double cx = 0, cy = 0, cz = 0;
1091
+ for (int m = 0; m < len; ++m) {
1092
+ cx += at(loop[m], 0);
1093
+ cy += at(loop[m], 1);
1094
+ cz += at(loop[m], 2);
1095
+ }
1096
+ const int64_t c = addVert(cx / len, cy / len, cz / len);
1097
+ for (int m = 0; m < len; ++m) tri(loop[m], loop[(m + 1) % len], c);
1098
+ }
1099
+ }
1100
+ }
1101
+ }
1102
+ }
1103
+ return mesh;
1104
+ }
1105
+
893
1106
  CSGParams resolveLevelSet(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
894
1107
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
895
1108
  CSGParams params;
@@ -934,12 +1147,20 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
934
1147
  // matches how the caller already has to think about the box.
935
1148
  const ListPtr* bb = std::get_if<ListPtr>(&params.at("bounds"));
936
1149
  std::optional<std::vector<double>> bLo, bHi;
1150
+ const bool noBounds = std::holds_alternative<std::monostate>(params.at("bounds"));
937
1151
  if (bb && *bb && (*bb)->items.size() == 2) {
938
1152
  bLo = numbersOrInf((*bb)->items[0]);
939
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
+ }
940
1159
  }
941
1160
  if (!bLo || !bHi || bLo->size() != bHi->size() || (bLo->size() != 2 && bLo->size() != 3)) {
942
- 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]]",
943
1164
  &node.position());
944
1165
  return {};
945
1166
  }
@@ -1037,75 +1258,82 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1037
1258
 
1038
1259
  // ---- 2D: sample onto a padded grid, contour it, hand to CrossSection.
1039
1260
  if (is2d) {
1040
- size_t nx = 0, ny = 0;
1041
- double sx = 0, sy = 0;
1042
1261
  if (field) {
1043
1262
  ev.warn("levelset(): a 2D field must be field[i][j]; a 3D array was given",
1044
1263
  &node.position());
1045
1264
  return {};
1046
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;
1047
1272
  if (!fieldFn) {
1048
- const std::optional<std::vector<std::vector<double>>> plane = readPlane(fieldArg);
1273
+ plane = readPlane(fieldArg);
1049
1274
  if (!plane || plane->size() < 2 || (*plane)[0].size() < 2) {
1050
1275
  ev.warn("levelset(): 2D field must be a rectangular field[i][j] of numbers",
1051
1276
  &node.position());
1052
1277
  return {};
1053
1278
  }
1054
- nx = plane->size();
1055
- ny = (*plane)[0].size();
1056
- sx = ((*bHi)[0] - (*bLo)[0]) / static_cast<double>(nx - 1);
1057
- sy = ((*bHi)[1] - (*bLo)[1]) / static_cast<double>(ny - 1);
1058
- Contours2d g;
1059
- g.nx = nx + 2;
1060
- g.ny = ny + 2;
1061
- g.dx = sx;
1062
- g.dy = sy;
1063
- g.ox = (*bLo)[0] - sx;
1064
- g.oy = (*bLo)[1] - sy;
1065
- // Padded with a ring that is firmly OUTSIDE, so a contour meeting
1066
- // the box edge closes along it instead of running off. This is
1067
- // BOSL2's closed=true, and the only behaviour offered.
1068
- g.v.assign(g.nx * g.ny, -1.0);
1069
- for (size_t i = 0; i < nx; ++i)
1070
- for (size_t j = 0; j < ny; ++j)
1071
- g.v[(j + 1) * g.nx + (i + 1)] = bandDistance((*plane)[i][j], isoLo, isoHi, invert);
1072
- const manifold::Polygons polys = marchingSquares(g);
1073
- if (polys.empty()) return {};
1074
- ColoredBody b;
1075
- b.section = clipToBounds(manifold::CrossSection(polys, manifold::CrossSection::FillRule::EvenOdd),
1076
- *bLo, *bHi);
1077
- if (b.section->IsEmpty()) return {};
1078
- std::vector<ColoredBody> out;
1079
- out.push_back(std::move(b));
1080
- 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));
1081
1286
  }
1082
- // function(x,y): edge= gives the spacing, as in 3D
1083
- const double ex = edgeVec ? edgeV[0] : edge, ey = edgeVec ? edgeV[1] : edge;
1084
- nx = static_cast<size_t>(std::floor(((*bHi)[0] - (*bLo)[0]) / ex)) + 1;
1085
- 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;
1086
1291
  if (nx < 2 || ny < 2) {
1087
1292
  ev.warn("levelset(): edge is larger than the bounds", &node.position());
1088
1293
  return {};
1089
1294
  }
1090
- sx = ((*bHi)[0] - (*bLo)[0]) / static_cast<double>(nx - 1);
1091
- 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
+ };
1092
1310
  Contours2d g;
1093
1311
  g.nx = nx + 2;
1094
1312
  g.ny = ny + 2;
1095
1313
  g.dx = sx;
1096
1314
  g.dy = sy;
1097
- g.ox = (*bLo)[0] - sx;
1098
- 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.
1099
1320
  g.v.assign(g.nx * g.ny, -1.0);
1100
1321
  for (size_t i = 0; i < nx; ++i) {
1101
1322
  for (size_t j = 0; j < ny; ++j) {
1102
- BoundArgs bound;
1103
- bound.set(fnParams2[0], Value{(*bLo)[0] + static_cast<double>(i) * sx});
1104
- bound.set(fnParams2[1], Value{(*bLo)[1] + static_cast<double>(j) * sy});
1105
- const Value outv =
1106
- ev.evalFunctionLiteralFromBound(**fieldFn, std::move(bound), *fnCtx, &node.position());
1107
- const double* d = std::get_if<double>(&outv);
1108
- const double val = (d && std::isfinite(*d)) ? *d : 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
+ }
1109
1337
  g.v[(j + 1) * g.nx + (i + 1)] = bandDistance(val, isoLo, isoHi, invert);
1110
1338
  }
1111
1339
  }
@@ -1121,6 +1349,7 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1121
1349
  }
1122
1350
 
1123
1351
 
1352
+ bool marchGrid = false;
1124
1353
  if (!fieldFn) {
1125
1354
  field = readField(fieldArg);
1126
1355
  if (!field) {
@@ -1135,6 +1364,15 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1135
1364
  spacing = {((*bHi)[0] - (*bLo)[0]) / static_cast<double>(field->nx - 1),
1136
1365
  ((*bHi)[1] - (*bLo)[1]) / static_cast<double>(field->ny - 1),
1137
1366
  ((*bHi)[2] - (*bLo)[2]) / static_cast<double>(field->nz - 1)};
1367
+ // Marched on the grid itself unless edge= asks for a different
1368
+ // spacing. A 1e-9 relative slack so an edge typed as the spacing,
1369
+ // or computed from the same bounds, still counts as it.
1370
+ const auto same = [](double a, double b) { return std::abs(a - b) <= 1e-9 * std::max(a, b); };
1371
+ marchGrid = edge <= 0.0;
1372
+ if (!marchGrid) {
1373
+ marchGrid = true;
1374
+ for (int a = 0; a < 3; ++a) marchGrid = marchGrid && same(edgeVec ? edgeV[a] : edge, spacing[a]);
1375
+ }
1138
1376
  // Also when edge= was given but unusable (warned above): edge 0
1139
1377
  // would ask Manifold for an infinitely fine lattice.
1140
1378
  if (edge <= 0.0) edge = std::min({spacing[0], spacing[1], spacing[2]});
@@ -1151,10 +1389,9 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1151
1389
  const double xyz[3] = {p.x, p.y, p.z};
1152
1390
  for (int a = 0; a < 3; ++a) bound.set(fnParams2[static_cast<size_t>(a)], Value{xyz[a]});
1153
1391
  const Value out = ev.evalFunctionLiteralFromBound(**fieldFn, std::move(bound), *fnCtx, &node.position());
1154
- const double* d = std::get_if<double>(&out);
1155
1392
  // A field that returns junk at one point must not abort the whole
1156
- // build; treat it as far outside instead.
1157
- const double v = (d && std::isfinite(*d)) ? *d : std::numeric_limits<double>::max();
1393
+ // build; it becomes a huge value instead. +-INF keeps its sign.
1394
+ const double v = fieldSample(out).value_or(std::numeric_limits<double>::max());
1158
1395
  return bandDistance(v, isoLo, isoHi, invert);
1159
1396
  };
1160
1397
 
@@ -1178,18 +1415,13 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1178
1415
  if (i0[a] > n[a] - 2) i0[a] = n[a] - 2;
1179
1416
  t[a] = g[a] - static_cast<double>(i0[a]);
1180
1417
  }
1181
- // Outside the sampled block the answer is "outside", falling away
1182
- // with distance. Clamping instead (which is what the interpolation
1183
- // above does on its own) would smear the boundary values outward and
1184
- // extend the surface rather than close it.
1185
- double outside = 0.0;
1186
- for (int a = 0; a < 3; ++a) {
1187
- const double maxg = static_cast<double>(n[a] - 1);
1188
- const double raw = (pos[a] - origin[a]) / spacing[a];
1189
- if (raw < 0.0) outside = std::max(outside, -raw);
1190
- else if (raw > maxg) outside = std::max(outside, raw - maxg);
1191
- }
1192
- if (outside > 0.0) return invert ? (1.0 + outside) : -(1.0 + outside);
1418
+ // Outside the sampled block the clamping above extends the boundary
1419
+ // values outward, so a surface meeting the box runs straight on into
1420
+ // the padding and the clip to `bounds` below leaves a clean plane.
1421
+ // Answering "outside" there instead (as this once did) made Manifold
1422
+ // close the surface right AT the box face, along its own lattice: a
1423
+ // waffle-textured staircase INSIDE the bounds, which the clip could
1424
+ // not remove (#745 on BelfrySCAD).
1193
1425
 
1194
1426
  double acc = 0.0;
1195
1427
  for (int c = 0; c < 8; ++c) {
@@ -1214,11 +1446,15 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1214
1446
  const manifold::vec3 pad = 2.0 * E;
1215
1447
  manifold::Box bounds(manifold::vec3(origin[0], origin[1], origin[2]) - pad,
1216
1448
  manifold::vec3((*hi)[0], (*hi)[1], (*hi)[2]) + pad);
1217
- // tolerance -1: a positive value makes Manifold do EXTRA evaluations per
1218
- // output vertex to snap nearer the true surface. Against a fixed grid
1219
- // those only re-interpolate data already used -- cost, no information.
1220
- // canParallel true: the lambda is pure C++ and never re-enters the
1221
- // evaluator, which is the whole point of taking a grid.
1449
+ // tolerance: Manifold root-finds along each crossing lattice edge until
1450
+ // the bracket is within it (a distance along the edge, so independent of
1451
+ // the field's units), instead of interpolating linearly between the two
1452
+ // lattice samples. 1% of the edge. Measured on #745 on BelfrySCAD: the
1453
+ // function form's vertices go from up to 0.2 off the true surface to on
1454
+ // it, and the mean face-normal error on a per-axis edge halves to
1455
+ // BOSL2's own; a grid gains too, since the trilinear field between
1456
+ // samples is not linear along a lattice edge. Costs 11-20% more work.
1457
+ //
1222
1458
  // canParallel is the crux: safe for a grid because the lambda is pure
1223
1459
  // C++, and NOT safe for a function, which re-enters the evaluator.
1224
1460
  // Manifold: parallel policies "will crash language runtimes with runtime
@@ -1226,19 +1462,32 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1226
1462
  //
1227
1463
  // LevelSet takes a single edge length, so a per-axis edge meshes a field
1228
1464
  // squashed to unit spacing on every axis and stretches the result back.
1229
- // Exact, not an approximation: with tolerance -1 a vertex is a linear
1230
- // interpolation along a lattice edge, and a linear interpolation commutes
1231
- // with an axis scale.
1465
+ // The tolerance there is 1% of the unit edge, so 1% of each axis's own.
1466
+ const double tol = 0.01 * (edgeVec ? 1.0 : edge);
1232
1467
  manifold::Manifold solid;
1233
- if (!edgeVec) {
1234
- solid = fieldFn ? manifold::Manifold::LevelSet(sampleFn, bounds, edge, 0.0, -1.0, /*canParallel=*/false)
1235
- : manifold::Manifold::LevelSet(sampleGrid, bounds, edge, 0.0, -1.0, /*canParallel=*/true);
1236
- } else {
1468
+ if (marchGrid) {
1469
+ const size_t n[3] = {field->nx + 2, field->ny + 2, field->nz + 2};
1470
+ std::vector<double> d(n[0] * n[1] * n[2], -1.0); // the "outside" ring
1471
+ for (size_t k = 0; k < field->nz; ++k)
1472
+ for (size_t j = 0; j < field->ny; ++j)
1473
+ for (size_t i = 0; i < field->nx; ++i)
1474
+ d[((k + 1) * n[1] + (j + 1)) * n[0] + (i + 1)] =
1475
+ bandDistance(field->at(i, j, k), isoLo, isoHi, invert);
1476
+ const std::array<double, 3> org{origin[0] - spacing[0], origin[1] - spacing[1], origin[2] - spacing[2]};
1477
+ solid = manifold::Manifold(marchingCubes(d, n, org, spacing));
1478
+ // Never expected; but a mesh Manifold refuses must not cost the
1479
+ // user their shape, so fall back to resampling it.
1480
+ if (solid.Status() != manifold::Manifold::Error::NoError) marchGrid = false;
1481
+ }
1482
+ if (!marchGrid && !edgeVec) {
1483
+ solid = fieldFn ? manifold::Manifold::LevelSet(sampleFn, bounds, edge, 0.0, tol, /*canParallel=*/false)
1484
+ : manifold::Manifold::LevelSet(sampleGrid, bounds, edge, 0.0, tol, /*canParallel=*/true);
1485
+ } else if (!marchGrid) {
1237
1486
  const manifold::Box unit(bounds.min / E, bounds.max / E);
1238
1487
  solid = fieldFn ? manifold::Manifold::LevelSet([&](manifold::vec3 q) { return sampleFn(q * E); },
1239
- unit, 1.0, 0.0, -1.0, /*canParallel=*/false)
1488
+ unit, 1.0, 0.0, tol, /*canParallel=*/false)
1240
1489
  : manifold::Manifold::LevelSet([&](manifold::vec3 q) { return sampleGrid(q * E); },
1241
- unit, 1.0, 0.0, -1.0, /*canParallel=*/true);
1490
+ unit, 1.0, 0.0, tol, /*canParallel=*/true);
1242
1491
  solid = solid.Scale(E);
1243
1492
  }
1244
1493