openscad-cpp-evaluator 1.45.3__tar.gz → 1.47.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.45.3 → openscad_cpp_evaluator-1.47.0}/CLAUDE.md +5 -0
  2. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/function_builtins.cpp +3 -4
  5. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/topology.cpp +274 -41
  6. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_booleans.cpp +284 -7
  7. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_feature_detection.cpp +3 -3
  8. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/uv.lock +1 -1
  9. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/.github/workflows/ci.yml +0 -0
  10. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/.github/workflows/wheels.yml +0 -0
  11. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/.gitignore +0 -0
  12. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/.gitmodules +0 -0
  13. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/CMakeLists.txt +0 -0
  14. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/LICENSE +0 -0
  15. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/README.md +0 -0
  16. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/bindings/CMakeLists.txt +0 -0
  17. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/bindings/ast_to_py.cpp +0 -0
  18. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/bindings/module.cpp +0 -0
  19. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/cmake/embed_font.cmake +0 -0
  20. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/cmake/patch_manifold.cmake +0 -0
  21. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/examples/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/examples/eval_perf_benchmark.cpp +0 -0
  23. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/examples/manifold_cache_reuse.cpp +0 -0
  24. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/examples/minimal_debugger.cpp +0 -0
  25. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  26. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/.gitignore +0 -0
  27. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  28. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/LICENSE +0 -0
  30. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/README.md +0 -0
  31. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  32. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  33. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  34. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  35. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  36. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  37. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  38. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  39. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  40. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  41. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  42. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  43. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  44. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  45. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  46. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  47. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  48. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  49. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  50. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  51. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  52. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  53. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  54. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  55. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  56. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  57. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  58. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  59. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  60. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  61. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  62. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  63. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  64. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  65. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  66. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  67. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  68. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  69. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  70. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  71. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  72. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  73. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  74. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  75. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  76. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  77. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  78. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  79. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  80. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  81. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  82. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  83. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  84. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  85. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  86. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  87. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_profile_time.cpp +0 -0
  88. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  89. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  90. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  91. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  92. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  93. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  94. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  95. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  96. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  97. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  98. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  99. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  100. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  101. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  102. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  103. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  104. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  105. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  106. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  107. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  108. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  109. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  110. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  111. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  112. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  113. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  114. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  115. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  116. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  117. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  118. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  119. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  120. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  121. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  122. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  123. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  124. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  125. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  126. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  127. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  128. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  129. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  130. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  131. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  132. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  133. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  134. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  135. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/resources/fonts/LICENSE +0 -0
  136. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  137. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  138. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  139. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  140. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/CMakeLists.txt +0 -0
  141. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/booleans.cpp +0 -0
  142. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/builtins.hpp +0 -0
  143. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/call_args.cpp +0 -0
  144. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/color.cpp +0 -0
  145. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/control.cpp +0 -0
  146. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/dxf_dim.cpp +0 -0
  147. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/extrude.cpp +0 -0
  148. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/import.cpp +0 -0
  149. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/modifiers.cpp +0 -0
  150. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/primitives_2d.cpp +0 -0
  151. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/primitives_3d.cpp +0 -0
  152. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/profile_time.cpp +0 -0
  153. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/registry.cpp +0 -0
  154. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/roof.cpp +0 -0
  155. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/segments.cpp +0 -0
  156. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/surface.cpp +0 -0
  157. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/text.cpp +0 -0
  158. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/builtins/transforms.cpp +0 -0
  159. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/bytecode_compiler.cpp +0 -0
  160. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/bytecode_vm.cpp +0 -0
  161. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/colored_body.cpp +0 -0
  162. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/coverage.cpp +0 -0
  163. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/csg_generate.cpp +0 -0
  164. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/csg_resolve.cpp +0 -0
  165. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/css_colors.cpp +0 -0
  166. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/debug/debug_repl.cpp +0 -0
  167. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/debug_profile.cpp +0 -0
  168. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/eval_context.cpp +0 -0
  169. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/eval_error.cpp +0 -0
  170. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/eval_use.cpp +0 -0
  171. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/export.cpp +0 -0
  172. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/expr_eval.cpp +0 -0
  173. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/format_closure.cpp +0 -0
  174. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/import/dxf_import.cpp +0 -0
  175. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/import/mesh_import.cpp +0 -0
  176. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/import/scene_import.cpp +0 -0
  177. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/import/surface_load.cpp +0 -0
  178. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/import/svg_import.cpp +0 -0
  179. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/manifold_cache.cpp +0 -0
  180. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/measure_geometry.cpp +0 -0
  181. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/mesh_check.cpp +0 -0
  182. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/stmt_eval.cpp +0 -0
  183. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/text/font_match.cpp +0 -0
  184. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/text/freetype_font_provider.cpp +0 -0
  185. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/text/text_metrics.cpp +0 -0
  186. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/user_calls.cpp +0 -0
  187. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/utf8.cpp +0 -0
  188. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/value.cpp +0 -0
  189. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/xkcd_colors.cpp +0 -0
  190. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/xkcd_colors.hpp +0 -0
  191. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/src/zip_stored.cpp +0 -0
  192. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/CMakeLists.txt +0 -0
  193. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_bytecode_compiler.cpp +0 -0
  194. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_children_separate.cpp +0 -0
  195. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_cli.cpp +0 -0
  196. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_control_flow.cpp +0 -0
  197. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_coverage.cpp +0 -0
  198. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_csg_tree.cpp +0 -0
  199. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_debug_hooks.cpp +0 -0
  200. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_discretizer.cpp +0 -0
  201. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_dxf_svg_import.cpp +0 -0
  202. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_eval_context.cpp +0 -0
  203. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_export.cpp +0 -0
  204. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_expr_eval.cpp +0 -0
  205. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_extrude_roof.cpp +0 -0
  206. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_function_builtins.cpp +0 -0
  207. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_helpers.hpp +0 -0
  208. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_import_export.cpp +0 -0
  209. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_include_cache.cpp +0 -0
  210. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_manifold_cache.cpp +0 -0
  211. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_mesh_check.cpp +0 -0
  212. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_multi_color_merge.cpp +0 -0
  213. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_openscad_parity.cpp +0 -0
  214. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_primitives.cpp +0 -0
  215. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_profile_time.cpp +0 -0
  216. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_profiling.cpp +0 -0
  217. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_python_bindings.py +0 -0
  218. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_render_expr.cpp +0 -0
  219. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_scope_trail.cpp +0 -0
  220. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_scoping.cpp +0 -0
  221. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_surface.cpp +0 -0
  222. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_tail_calls.cpp +0 -0
  223. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_text.cpp +0 -0
  224. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_transforms.cpp +0 -0
  225. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_undef_args.cpp +0 -0
  226. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_unexpected_args.cpp +0 -0
  227. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_use.cpp +0 -0
  228. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_value.cpp +0 -0
  229. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_viewport_params.cpp +0 -0
  230. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tests/test_zip_stored.cpp +0 -0
  231. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tools/cli/CMakeLists.txt +0 -0
  232. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tools/cli/cli_lib.cpp +0 -0
  233. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tools/cli/cli_lib.hpp +0 -0
  234. {openscad_cpp_evaluator-1.45.3 → openscad_cpp_evaluator-1.47.0}/tools/cli/main.cpp +0 -0
@@ -601,6 +601,11 @@ grep for `ponytail:`.
601
601
  One `LevelSet` pass either way; a bounded range *could* be
602
602
  `difference(levelset(hi), levelset(lo))` but that meshes twice and then booleans.
603
603
  3D wraps `Manifold::LevelSet` with a C++ sampling lambda; no new Manifold API.
604
+ **`edge` may be a vector**, one spacing per axis (`[ex,ey,ez]`, or `[ex,ey]` in 2D), as BOSL2's
605
+ `isosurface()` takes `voxel_size`. `LevelSet` takes one edge length, so the 3D path meshes the
606
+ field squashed to unit spacing and `Scale`s the result back -- exact, since with tolerance -1 a
607
+ vertex is a linear interpolation along a lattice edge, which commutes with an axis scale. A plain
608
+ number takes the old path unchanged. `supported_feature("levelset")` is **2** from this change.
604
609
  **The field is a grid OR a `function(x,y,z)`, and the two trade off against each other** —
605
610
  neither is simply better:
606
611
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.45.3
3
+ Version: 1.47.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.45.3"
7
+ version = "1.47.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -976,9 +976,8 @@ namespace {
976
976
  // is supposed to be safe).
977
977
  //
978
978
  // A LEVEL rather than a boolean so a feature whose semantics change later
979
- // can be told apart from its earlier self. Everything here is 1 today;
980
- // nothing has changed since this function existed, and a script cannot
981
- // observe what a build without supported_feature() did anyway.
979
+ // can be told apart from its earlier self. A script cannot observe what a
980
+ // build without supported_feature() did, so every feature starts at 1.
982
981
  //
983
982
  // This is the answer to a real hazard: OpenSCAD does not reject arguments or
984
983
  // names it doesn't know -- children(separate=true) is silently ignored there
@@ -988,7 +987,7 @@ const std::unordered_map<std::string, double>& featureLevels() {
988
987
  static const std::unordered_map<std::string, double> levels = {
989
988
  {"render-expr", 1.0}, // render() in expression position
990
989
  {"linear-solve", 1.0}, // linear_solve(A, b) -> {x, det, singular}
991
- {"levelset", 1.0}, // levelset(field, bounds, isovalue)
990
+ {"levelset", 2.0}, // levelset(field, bounds, isovalue); 2: edge may be a vector
992
991
  {"polyhedron-vnf", 1.0}, // polyhedron(vnf) / polyhedron(object)
993
992
  {"separate-children", 1.0}, // children(..., separate=true)
994
993
  {"minkowski-diff", 1.0}, // minkowski_difference()
@@ -6,6 +6,8 @@
6
6
 
7
7
  #include <manifold/polygon.h>
8
8
 
9
+ #include <algorithm>
10
+
9
11
  namespace oscadeval {
10
12
 
11
13
  // simplify()'s default tolerance, as a fraction of the body's bounding-box
@@ -663,14 +665,30 @@ std::vector<ColoredBody> generateMeshRepair(Evaluator& ev, const CSGParams& para
663
665
  // The trade is memory: ~67 bytes per cell, so 200^3 is about 550 MB and that
664
666
  // is the practical ceiling. A callback samples lazily and has no ceiling.
665
667
  //
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.
668
+ // Accuracy is bounded by the GRID, not by Manifold. At the grid's own
669
+ // spacing (the default) the grid is marched directly, as BOSL2's
670
+ // isosurface() does -- see marchingCubes(). An edge= asking for a different
671
+ // spacing resamples it through LevelSet instead, which samples a
672
+ // body-centred cubic lattice (sdf.cpp:440-446) that a plain cubic grid does
673
+ // not line up with, so the lambda interpolates trilinearly between samples.
671
674
 
672
675
  namespace {
673
676
 
677
+ // A sample as the field arrays and field functions hand it over. +-INF is a
678
+ // legal sample -- a metaball field is 1/0 wherever a grid point lands on a
679
+ // centre (#744 on BelfrySCAD) -- but it cannot be used as is: the trilinear
680
+ // sampler weights corners, and 0 * INF is NaN. So it becomes a huge FINITE
681
+ // value, which is the limit BOSL2's isosurface() takes (an edge to an INF
682
+ // corner crosses at the other end). A quarter of DBL_MAX so that sums of
683
+ // weighted corners and bandDistance's subtraction cannot overflow. NaN, or
684
+ // anything not a number, has no meaning and is nullopt.
685
+ std::optional<double> fieldSample(const Value& item) {
686
+ const double* d = std::get_if<double>(&item);
687
+ if (!d || std::isnan(*d)) return std::nullopt;
688
+ constexpr double kHuge = std::numeric_limits<double>::max() / 4;
689
+ return std::clamp(*d, -kHuge, kHuge);
690
+ }
691
+
674
692
  struct ScalarField {
675
693
  std::vector<double> v; // flat, x fastest
676
694
  size_t nx = 0, ny = 0, nz = 0;
@@ -698,8 +716,8 @@ std::optional<ScalarField> readField(const Value& val) {
698
716
  return std::nullopt;
699
717
  }
700
718
  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;
719
+ const std::optional<double> d = fieldSample((*zs)->items[k]);
720
+ if (!d) return std::nullopt;
703
721
  f.v[(k * f.ny + j) * f.nx + i] = *d;
704
722
  }
705
723
  }
@@ -722,8 +740,8 @@ std::optional<std::vector<std::vector<double>>> readPlane(const Value& val) {
722
740
  std::vector<double> row;
723
741
  row.reserve(ny);
724
742
  for (const Value& item : (*ys)->items) {
725
- const double* d = std::get_if<double>(&item);
726
- if (!d || !std::isfinite(*d)) return std::nullopt;
743
+ const std::optional<double> d = fieldSample(item);
744
+ if (!d) return std::nullopt;
727
745
  row.push_back(*d);
728
746
  }
729
747
  out.push_back(std::move(row));
@@ -890,6 +908,162 @@ manifold::Polygons marchingSquares(const Contours2d& g) {
890
908
  }
891
909
 
892
910
 
911
+ // -- 3D: marching cubes on the grid itself -----------------------------------
912
+ //
913
+ // For a grid sampled at the spacing it was given, Manifold's LevelSet is the
914
+ // wrong tool: it samples its own body-centred lattice, which does not line up
915
+ // with the grid, so it meshes a trilinear blend of the data rather than the
916
+ // data. Measured on #745 on BelfrySCAD, that left a metaball field visibly
917
+ // more faceted than BOSL2's isosurface(), which marches the grid itself. So
918
+ // this does too.
919
+ //
920
+ // No 256-case table. The table's hard part is the ambiguous face (two
921
+ // inside corners on a diagonal), and a table that resolves it per CELL can
922
+ // disagree with the neighbouring cell and leave a crack -- fatal here, since
923
+ // Manifold needs a closed mesh. So the triangles are built per FACE instead:
924
+ // each face's crossings are paired into segments by a rule that reads only
925
+ // that face's four values (the face-centre average decides a saddle, as the
926
+ // 2D path does), so the two cells sharing a face always agree. A cell's
927
+ // segments then link into closed loops, one polygon each. Vertices are keyed
928
+ // on GRID EDGE identity, so neighbours share them by construction, exactly as
929
+ // marchingSquares keys on edges rather than coordinates.
930
+ //
931
+ // Orientation: walking a face's corners anticlockwise as seen from outside
932
+ // the cell, a segment runs from an outside->inside crossing to an
933
+ // inside->outside one. A loop of such segments, read in order, is a polygon
934
+ // whose normal points from inside to outside -- Manifold's convention, with
935
+ // inside the positive side.
936
+ //
937
+ // The field arrives as band distance (positive inside) with a ring of
938
+ // "outside" round it, like the 2D path, so every surface closes; the caller
939
+ // clips the overhang back to the bounds.
940
+ manifold::MeshGL64 marchingCubes(const std::vector<double>& d, const size_t n[3],
941
+ const std::array<double, 3>& org, const std::array<double, 3>& h) {
942
+ const auto P = [&](size_t i, size_t j, size_t k) { return (k * n[1] + j) * n[0] + i; };
943
+ // corner c of a cell is offset (c&1, c>>1&1, c>>2&1). Each face's
944
+ // corners, anticlockwise as seen from outside.
945
+ static constexpr int kFaces[6][4] = {
946
+ {0, 2, 3, 1}, {4, 5, 7, 6}, // -z, +z
947
+ {0, 4, 6, 2}, {1, 3, 7, 5}, // -x, +x
948
+ {0, 1, 5, 4}, {2, 6, 7, 3}, // -y, +y
949
+ };
950
+ manifold::MeshGL64 mesh;
951
+ mesh.numProp = 3;
952
+ // ponytail: one int per grid edge, ~12 bytes a sample next to the
953
+ // field's own ~67; key the vertices per slab if this ever binds.
954
+ std::vector<int64_t> edgeVert(3 * n[0] * n[1] * n[2], -1);
955
+ const auto addVert = [&](double x, double y, double z) {
956
+ mesh.vertProperties.insert(mesh.vertProperties.end(), {x, y, z});
957
+ return static_cast<int64_t>(mesh.vertProperties.size() / 3 - 1);
958
+ };
959
+ for (size_t k = 0; k + 1 < n[2]; ++k) {
960
+ for (size_t j = 0; j + 1 < n[1]; ++j) {
961
+ for (size_t i = 0; i + 1 < n[0]; ++i) {
962
+ double v[8];
963
+ size_t p[8];
964
+ int inside = 0;
965
+ for (int c = 0; c < 8; ++c) {
966
+ p[c] = P(i + (c & 1), j + (c >> 1 & 1), k + (c >> 2 & 1));
967
+ v[c] = d[p[c]];
968
+ inside += v[c] > 0.0;
969
+ }
970
+ if (inside == 0 || inside == 8) continue;
971
+ // The vertex on the edge between corners a and b.
972
+ const auto vert = [&](int a, int b) -> int64_t {
973
+ if (a > b) std::swap(a, b); // a is the lower end
974
+ const int axis = (b - a) == 1 ? 0 : (b - a) == 2 ? 1 : 2;
975
+ int64_t& id = edgeVert[3 * p[a] + static_cast<size_t>(axis)];
976
+ if (id < 0) {
977
+ const double t = v[a] / (v[a] - v[b]);
978
+ const double x = org[0] + h[0] * (static_cast<double>(i + (a & 1)) + (axis == 0 ? t : 0.0));
979
+ const double y = org[1] + h[1] * (static_cast<double>(j + (a >> 1 & 1)) + (axis == 1 ? t : 0.0));
980
+ const double z = org[2] + h[2] * (static_cast<double>(k + (a >> 2 & 1)) + (axis == 2 ? t : 0.0));
981
+ id = addVert(x, y, z);
982
+ }
983
+ return id;
984
+ };
985
+ // Segments start->end, at most two per face.
986
+ int64_t segFrom[12], segTo[12];
987
+ int nSeg = 0;
988
+ for (const auto& f : kFaces) {
989
+ int64_t cross[4];
990
+ bool outIn[4];
991
+ int nc = 0;
992
+ for (int m = 0; m < 4; ++m) {
993
+ const int a = f[m], b = f[(m + 1) % 4];
994
+ if ((v[a] > 0.0) != (v[b] > 0.0)) {
995
+ cross[nc] = vert(a, b);
996
+ outIn[nc++] = v[b] > 0.0;
997
+ }
998
+ }
999
+ if (nc == 0) continue;
1000
+ // A saddle joins its inside corners when the face centre
1001
+ // is inside: each out->in crossing then pairs with the
1002
+ // PREVIOUS crossing instead of the next.
1003
+ const bool joined =
1004
+ nc == 4 && v[f[0]] + v[f[1]] + v[f[2]] + v[f[3]] > 0.0;
1005
+ for (int m = 0; m < nc; ++m) {
1006
+ if (!outIn[m]) continue;
1007
+ segFrom[nSeg] = cross[m];
1008
+ segTo[nSeg++] = cross[(m + (joined ? nc - 1 : 1)) % nc];
1009
+ }
1010
+ }
1011
+ // Link into loops; each vertex starts exactly one segment.
1012
+ bool used[12] = {};
1013
+ for (int s0 = 0; s0 < nSeg; ++s0) {
1014
+ if (used[s0]) continue;
1015
+ int64_t loop[12];
1016
+ int len = 0;
1017
+ for (int s = s0; s >= 0 && !used[s];) {
1018
+ used[s] = true;
1019
+ loop[len++] = segFrom[s];
1020
+ const int64_t want = segTo[s];
1021
+ s = -1;
1022
+ for (int q = 0; q < nSeg; ++q)
1023
+ if (!used[q] && segFrom[q] == want) { s = q; break; }
1024
+ }
1025
+ const auto tri = [&](int64_t a, int64_t b, int64_t c) {
1026
+ mesh.triVerts.insert(mesh.triVerts.end(), {static_cast<uint64_t>(a), static_cast<uint64_t>(b),
1027
+ static_cast<uint64_t>(c)});
1028
+ };
1029
+ const auto at = [&](int64_t id, int axis) {
1030
+ return mesh.vertProperties[static_cast<size_t>(id) * 3 + static_cast<size_t>(axis)];
1031
+ };
1032
+ const auto dist2 = [&](int64_t a, int64_t b) {
1033
+ double s2 = 0;
1034
+ for (int ax = 0; ax < 3; ++ax) s2 += (at(a, ax) - at(b, ax)) * (at(a, ax) - at(b, ax));
1035
+ return s2;
1036
+ };
1037
+ if (len == 3) {
1038
+ tri(loop[0], loop[1], loop[2]);
1039
+ } else if (len == 4) {
1040
+ // Split along the shorter diagonal.
1041
+ if (dist2(loop[0], loop[2]) <= dist2(loop[1], loop[3])) {
1042
+ tri(loop[0], loop[1], loop[2]);
1043
+ tri(loop[0], loop[2], loop[3]);
1044
+ } else {
1045
+ tri(loop[1], loop[2], loop[3]);
1046
+ tri(loop[1], loop[3], loop[0]);
1047
+ }
1048
+ } else if (len > 4) {
1049
+ // A fan from one corner of a 5-7 sided, non-planar
1050
+ // loop can fold; a fan from its centroid cannot.
1051
+ double cx = 0, cy = 0, cz = 0;
1052
+ for (int m = 0; m < len; ++m) {
1053
+ cx += at(loop[m], 0);
1054
+ cy += at(loop[m], 1);
1055
+ cz += at(loop[m], 2);
1056
+ }
1057
+ const int64_t c = addVert(cx / len, cy / len, cz / len);
1058
+ for (int m = 0; m < len; ++m) tri(loop[m], loop[(m + 1) % len], c);
1059
+ }
1060
+ }
1061
+ }
1062
+ }
1063
+ }
1064
+ return mesh;
1065
+ }
1066
+
893
1067
  CSGParams resolveLevelSet(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
894
1068
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
895
1069
  CSGParams params;
@@ -991,11 +1165,31 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
991
1165
  const std::array<double, 3> origin = *lo;
992
1166
  std::array<double, 3> spacing{1.0, 1.0, 1.0};
993
1167
  double edge = 0.0;
1168
+ // edge may also be one spacing per axis, as BOSL2's isosurface() takes
1169
+ // voxel_size -- a thin plate wants fine sampling across it and coarse
1170
+ // along it (#729 on BelfrySCAD). edgeV is then the vector and `edge`
1171
+ // its smallest component; for a plain number edgeV is unused.
1172
+ std::array<double, 3> edgeV{0.0, 0.0, 0.0};
1173
+ bool edgeVec = false;
994
1174
  // Grid spacing is filled in below, once the array has been read at the
995
1175
  // right dimensionality. Finer than the grid buys nothing.
996
- if (const double* e = std::get_if<double>(&params.at("edge"))) {
1176
+ const Value& edgeArg = params.at("edge");
1177
+ if (const double* e = std::get_if<double>(&edgeArg)) {
997
1178
  if (*e > 0.0) edge = *e;
998
1179
  else ev.warn("levelset(): edge must be positive", &node.position());
1180
+ } else if (!std::holds_alternative<std::monostate>(edgeArg)) {
1181
+ const std::optional<std::vector<double>> ev3 = numbersOrInf(edgeArg);
1182
+ bool ok = ev3 && ev3->size() == bLo->size();
1183
+ for (size_t a = 0; ok && a < ev3->size(); ++a) ok = std::isfinite((*ev3)[a]) && (*ev3)[a] > 0.0;
1184
+ if (ok) {
1185
+ edgeVec = true;
1186
+ for (size_t a = 0; a < ev3->size(); ++a) edgeV[a] = (*ev3)[a];
1187
+ edge = *std::min_element(ev3->begin(), ev3->end());
1188
+ } else {
1189
+ ev.warn(is2d ? "levelset(): edge must be a positive number or [ex, ey]"
1190
+ : "levelset(): edge must be a positive number or [ex, ey, ez]",
1191
+ &node.position());
1192
+ }
999
1193
  }
1000
1194
  if (edge <= 0.0 && fieldFn) {
1001
1195
  // No grid to infer it from. Guessing would silently pick either a
@@ -1060,8 +1254,9 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1060
1254
  return out;
1061
1255
  }
1062
1256
  // function(x,y): edge= gives the spacing, as in 3D
1063
- nx = static_cast<size_t>(std::floor(((*bHi)[0] - (*bLo)[0]) / edge)) + 1;
1064
- ny = static_cast<size_t>(std::floor(((*bHi)[1] - (*bLo)[1]) / edge)) + 1;
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;
1065
1260
  if (nx < 2 || ny < 2) {
1066
1261
  ev.warn("levelset(): edge is larger than the bounds", &node.position());
1067
1262
  return {};
@@ -1083,8 +1278,7 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1083
1278
  bound.set(fnParams2[1], Value{(*bLo)[1] + static_cast<double>(j) * sy});
1084
1279
  const Value outv =
1085
1280
  ev.evalFunctionLiteralFromBound(**fieldFn, std::move(bound), *fnCtx, &node.position());
1086
- const double* d = std::get_if<double>(&outv);
1087
- const double val = (d && std::isfinite(*d)) ? *d : std::numeric_limits<double>::max();
1281
+ const double val = fieldSample(outv).value_or(std::numeric_limits<double>::max());
1088
1282
  g.v[(j + 1) * g.nx + (i + 1)] = bandDistance(val, isoLo, isoHi, invert);
1089
1283
  }
1090
1284
  }
@@ -1100,6 +1294,7 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1100
1294
  }
1101
1295
 
1102
1296
 
1297
+ bool marchGrid = false;
1103
1298
  if (!fieldFn) {
1104
1299
  field = readField(fieldArg);
1105
1300
  if (!field) {
@@ -1114,7 +1309,18 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1114
1309
  spacing = {((*bHi)[0] - (*bLo)[0]) / static_cast<double>(field->nx - 1),
1115
1310
  ((*bHi)[1] - (*bLo)[1]) / static_cast<double>(field->ny - 1),
1116
1311
  ((*bHi)[2] - (*bLo)[2]) / static_cast<double>(field->nz - 1)};
1117
- if (!std::get_if<double>(&params.at("edge"))) edge = std::min({spacing[0], spacing[1], spacing[2]});
1312
+ // Marched on the grid itself unless edge= asks for a different
1313
+ // spacing. A 1e-9 relative slack so an edge typed as the spacing,
1314
+ // or computed from the same bounds, still counts as it.
1315
+ const auto same = [](double a, double b) { return std::abs(a - b) <= 1e-9 * std::max(a, b); };
1316
+ marchGrid = edge <= 0.0;
1317
+ if (!marchGrid) {
1318
+ marchGrid = true;
1319
+ for (int a = 0; a < 3; ++a) marchGrid = marchGrid && same(edgeVec ? edgeV[a] : edge, spacing[a]);
1320
+ }
1321
+ // Also when edge= was given but unusable (warned above): edge 0
1322
+ // would ask Manifold for an infinitely fine lattice.
1323
+ if (edge <= 0.0) edge = std::min({spacing[0], spacing[1], spacing[2]});
1118
1324
  }
1119
1325
 
1120
1326
  if (fieldFn && fnParams2.size() < 3) {
@@ -1128,10 +1334,9 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1128
1334
  const double xyz[3] = {p.x, p.y, p.z};
1129
1335
  for (int a = 0; a < 3; ++a) bound.set(fnParams2[static_cast<size_t>(a)], Value{xyz[a]});
1130
1336
  const Value out = ev.evalFunctionLiteralFromBound(**fieldFn, std::move(bound), *fnCtx, &node.position());
1131
- const double* d = std::get_if<double>(&out);
1132
1337
  // A field that returns junk at one point must not abort the whole
1133
- // build; treat it as far outside instead.
1134
- const double v = (d && std::isfinite(*d)) ? *d : std::numeric_limits<double>::max();
1338
+ // build; it becomes a huge value instead. +-INF keeps its sign.
1339
+ const double v = fieldSample(out).value_or(std::numeric_limits<double>::max());
1135
1340
  return bandDistance(v, isoLo, isoHi, invert);
1136
1341
  };
1137
1342
 
@@ -1155,18 +1360,13 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1155
1360
  if (i0[a] > n[a] - 2) i0[a] = n[a] - 2;
1156
1361
  t[a] = g[a] - static_cast<double>(i0[a]);
1157
1362
  }
1158
- // Outside the sampled block the answer is "outside", falling away
1159
- // with distance. Clamping instead (which is what the interpolation
1160
- // above does on its own) would smear the boundary values outward and
1161
- // extend the surface rather than close it.
1162
- double outside = 0.0;
1163
- for (int a = 0; a < 3; ++a) {
1164
- const double maxg = static_cast<double>(n[a] - 1);
1165
- const double raw = (pos[a] - origin[a]) / spacing[a];
1166
- if (raw < 0.0) outside = std::max(outside, -raw);
1167
- else if (raw > maxg) outside = std::max(outside, raw - maxg);
1168
- }
1169
- if (outside > 0.0) return invert ? (1.0 + outside) : -(1.0 + outside);
1363
+ // Outside the sampled block the clamping above extends the boundary
1364
+ // values outward, so a surface meeting the box runs straight on into
1365
+ // the padding and the clip to `bounds` below leaves a clean plane.
1366
+ // Answering "outside" there instead (as this once did) made Manifold
1367
+ // close the surface right AT the box face, along its own lattice: a
1368
+ // waffle-textured staircase INSIDE the bounds, which the clip could
1369
+ // not remove (#745 on BelfrySCAD).
1170
1370
 
1171
1371
  double acc = 0.0;
1172
1372
  for (int c = 0; c < 8; ++c) {
@@ -1187,21 +1387,54 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1187
1387
  //
1188
1388
  // Exactly the fix already applied to the 2D path (clipToBounds); it was
1189
1389
  // not applied here at the time because the 2D error was the one measured.
1190
- const double pad = 2.0 * edge;
1191
- manifold::Box bounds(manifold::vec3(origin[0] - pad, origin[1] - pad, origin[2] - pad),
1192
- manifold::vec3((*hi)[0] + pad, (*hi)[1] + pad, (*hi)[2] + pad));
1193
- // tolerance -1: a positive value makes Manifold do EXTRA evaluations per
1194
- // output vertex to snap nearer the true surface. Against a fixed grid
1195
- // those only re-interpolate data already used -- cost, no information.
1196
- // canParallel true: the lambda is pure C++ and never re-enters the
1197
- // evaluator, which is the whole point of taking a grid.
1390
+ const manifold::vec3 E = edgeVec ? manifold::vec3(edgeV[0], edgeV[1], edgeV[2]) : manifold::vec3(edge);
1391
+ const manifold::vec3 pad = 2.0 * E;
1392
+ manifold::Box bounds(manifold::vec3(origin[0], origin[1], origin[2]) - pad,
1393
+ manifold::vec3((*hi)[0], (*hi)[1], (*hi)[2]) + pad);
1394
+ // tolerance: Manifold root-finds along each crossing lattice edge until
1395
+ // the bracket is within it (a distance along the edge, so independent of
1396
+ // the field's units), instead of interpolating linearly between the two
1397
+ // lattice samples. 1% of the edge. Measured on #745 on BelfrySCAD: the
1398
+ // function form's vertices go from up to 0.2 off the true surface to on
1399
+ // it, and the mean face-normal error on a per-axis edge halves to
1400
+ // BOSL2's own; a grid gains too, since the trilinear field between
1401
+ // samples is not linear along a lattice edge. Costs 11-20% more work.
1402
+ //
1198
1403
  // canParallel is the crux: safe for a grid because the lambda is pure
1199
1404
  // C++, and NOT safe for a function, which re-enters the evaluator.
1200
1405
  // Manifold: parallel policies "will crash language runtimes with runtime
1201
1406
  // locks that expect to not be called back by unregistered threads".
1202
- manifold::Manifold solid =
1203
- fieldFn ? manifold::Manifold::LevelSet(sampleFn, bounds, edge, 0.0, -1.0, /*canParallel=*/false)
1204
- : manifold::Manifold::LevelSet(sampleGrid, bounds, edge, 0.0, -1.0, /*canParallel=*/true);
1407
+ //
1408
+ // LevelSet takes a single edge length, so a per-axis edge meshes a field
1409
+ // squashed to unit spacing on every axis and stretches the result back.
1410
+ // The tolerance there is 1% of the unit edge, so 1% of each axis's own.
1411
+ const double tol = 0.01 * (edgeVec ? 1.0 : edge);
1412
+ manifold::Manifold solid;
1413
+ if (marchGrid) {
1414
+ const size_t n[3] = {field->nx + 2, field->ny + 2, field->nz + 2};
1415
+ std::vector<double> d(n[0] * n[1] * n[2], -1.0); // the "outside" ring
1416
+ for (size_t k = 0; k < field->nz; ++k)
1417
+ for (size_t j = 0; j < field->ny; ++j)
1418
+ for (size_t i = 0; i < field->nx; ++i)
1419
+ d[((k + 1) * n[1] + (j + 1)) * n[0] + (i + 1)] =
1420
+ bandDistance(field->at(i, j, k), isoLo, isoHi, invert);
1421
+ const std::array<double, 3> org{origin[0] - spacing[0], origin[1] - spacing[1], origin[2] - spacing[2]};
1422
+ solid = manifold::Manifold(marchingCubes(d, n, org, spacing));
1423
+ // Never expected; but a mesh Manifold refuses must not cost the
1424
+ // user their shape, so fall back to resampling it.
1425
+ if (solid.Status() != manifold::Manifold::Error::NoError) marchGrid = false;
1426
+ }
1427
+ if (!marchGrid && !edgeVec) {
1428
+ solid = fieldFn ? manifold::Manifold::LevelSet(sampleFn, bounds, edge, 0.0, tol, /*canParallel=*/false)
1429
+ : manifold::Manifold::LevelSet(sampleGrid, bounds, edge, 0.0, tol, /*canParallel=*/true);
1430
+ } else if (!marchGrid) {
1431
+ const manifold::Box unit(bounds.min / E, bounds.max / E);
1432
+ solid = fieldFn ? manifold::Manifold::LevelSet([&](manifold::vec3 q) { return sampleFn(q * E); },
1433
+ unit, 1.0, 0.0, tol, /*canParallel=*/false)
1434
+ : manifold::Manifold::LevelSet([&](manifold::vec3 q) { return sampleGrid(q * E); },
1435
+ unit, 1.0, 0.0, tol, /*canParallel=*/true);
1436
+ solid = solid.Scale(E);
1437
+ }
1205
1438
 
1206
1439
  if (solid.IsEmpty()) return {};
1207
1440
  solid = solid ^ manifold::Manifold::Cube(