openscad-cpp-evaluator 1.49.8__tar.gz → 1.50.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 (239) hide show
  1. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/CLAUDE.md +37 -0
  2. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/CMakeLists.txt +2 -1
  3. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/PKG-INFO +1 -1
  4. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/pyproject.toml +1 -1
  5. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/CMakeLists.txt +7 -0
  6. openscad_cpp_evaluator-1.50.0/src/builtins/alpha_wrap.cpp +593 -0
  7. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/builtins.hpp +5 -0
  8. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/function_builtins.cpp +1 -0
  9. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/modifiers.cpp +7 -1
  10. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/registry.cpp +3 -0
  11. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/csg_resolve.cpp +4 -1
  12. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/CMakeLists.txt +1 -0
  13. openscad_cpp_evaluator-1.50.0/tests/test_alpha_wrap.cpp +120 -0
  14. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_booleans.cpp +32 -0
  15. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_feature_detection.cpp +1 -1
  16. openscad_cpp_evaluator-1.50.0/third_party/predicates/predicates.c +4130 -0
  17. openscad_cpp_evaluator-1.50.0/third_party/predicates/predicates.h +22 -0
  18. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/.github/workflows/ci.yml +0 -0
  19. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/.github/workflows/wheels.yml +0 -0
  20. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/.gitignore +0 -0
  21. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/.gitmodules +0 -0
  22. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/LICENSE +0 -0
  23. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/README.md +0 -0
  24. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/bindings/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/bindings/ast_to_py.cpp +0 -0
  26. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/bindings/module.cpp +0 -0
  27. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/cmake/embed_font.cmake +0 -0
  28. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/cmake/patch_manifold.cmake +0 -0
  29. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/examples/CMakeLists.txt +0 -0
  30. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/examples/eval_perf_benchmark.cpp +0 -0
  31. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/examples/manifold_cache_reuse.cpp +0 -0
  32. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/examples/minimal_debugger.cpp +0 -0
  33. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  34. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/.gitignore +0 -0
  35. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  36. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  37. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/LICENSE +0 -0
  38. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/README.md +0 -0
  39. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  40. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  41. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  43. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  44. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  45. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  46. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  47. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  48. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  49. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  50. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  51. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  52. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  53. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  54. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  55. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  56. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  57. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  58. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  59. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  60. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  61. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  62. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  63. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  64. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  65. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  66. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  67. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  68. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  69. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  70. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  71. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  72. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  73. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  74. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  75. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  76. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  77. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  78. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  79. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  80. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  81. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  82. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  83. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  84. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  85. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  86. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  87. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  88. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  89. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  90. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  91. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  92. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  93. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  94. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  95. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_profile_time.cpp +0 -0
  96. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  97. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  98. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  99. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  100. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  101. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  102. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  103. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  104. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  105. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  106. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  107. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  108. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  109. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  110. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  111. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  112. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  113. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  114. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  115. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  116. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  117. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  118. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  119. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  120. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  121. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  122. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  123. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  124. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  125. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  126. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  127. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  128. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  129. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  130. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  131. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  132. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  133. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  134. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  135. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/reassign.hpp +0 -0
  136. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  137. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  138. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  139. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  140. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  141. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  142. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  143. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  144. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/resources/fonts/LICENSE +0 -0
  145. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  146. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  147. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  148. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  149. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/booleans.cpp +0 -0
  150. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/call_args.cpp +0 -0
  151. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/color.cpp +0 -0
  152. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/control.cpp +0 -0
  153. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/dxf_dim.cpp +0 -0
  154. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/extrude.cpp +0 -0
  155. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/import.cpp +0 -0
  156. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/primitives_2d.cpp +0 -0
  157. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/primitives_3d.cpp +0 -0
  158. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/profile_time.cpp +0 -0
  159. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/roof.cpp +0 -0
  160. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/segments.cpp +0 -0
  161. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/surface.cpp +0 -0
  162. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/text.cpp +0 -0
  163. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/topology.cpp +0 -0
  164. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/transforms.cpp +0 -0
  165. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/bytecode_compiler.cpp +0 -0
  166. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/bytecode_vm.cpp +0 -0
  167. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/colored_body.cpp +0 -0
  168. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/coverage.cpp +0 -0
  169. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/csg_generate.cpp +0 -0
  170. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/css_colors.cpp +0 -0
  171. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/debug/debug_repl.cpp +0 -0
  172. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/debug_profile.cpp +0 -0
  173. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/eval_context.cpp +0 -0
  174. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/eval_error.cpp +0 -0
  175. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/eval_use.cpp +0 -0
  176. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/export.cpp +0 -0
  177. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/expr_eval.cpp +0 -0
  178. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/format_closure.cpp +0 -0
  179. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/import/dxf_import.cpp +0 -0
  180. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/import/mesh_import.cpp +0 -0
  181. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/import/scene_import.cpp +0 -0
  182. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/import/surface_load.cpp +0 -0
  183. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/import/svg_import.cpp +0 -0
  184. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/manifold_cache.cpp +0 -0
  185. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/measure_geometry.cpp +0 -0
  186. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/mesh_check.cpp +0 -0
  187. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/stmt_eval.cpp +0 -0
  188. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/text/font_match.cpp +0 -0
  189. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/text/freetype_font_provider.cpp +0 -0
  190. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/text/text_metrics.cpp +0 -0
  191. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/user_calls.cpp +0 -0
  192. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/utf8.cpp +0 -0
  193. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/value.cpp +0 -0
  194. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/xkcd_colors.cpp +0 -0
  195. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/xkcd_colors.hpp +0 -0
  196. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/zip_stored.cpp +0 -0
  197. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_bytecode_compiler.cpp +0 -0
  198. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_children_separate.cpp +0 -0
  199. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_cli.cpp +0 -0
  200. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_control_flow.cpp +0 -0
  201. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_coverage.cpp +0 -0
  202. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_csg_tree.cpp +0 -0
  203. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_debug_hooks.cpp +0 -0
  204. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_discretizer.cpp +0 -0
  205. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_dxf_svg_import.cpp +0 -0
  206. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_eval_context.cpp +0 -0
  207. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_export.cpp +0 -0
  208. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_expr_eval.cpp +0 -0
  209. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_extrude_roof.cpp +0 -0
  210. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_function_builtins.cpp +0 -0
  211. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_helpers.hpp +0 -0
  212. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_import_export.cpp +0 -0
  213. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_include_cache.cpp +0 -0
  214. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_manifold_cache.cpp +0 -0
  215. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_mesh_check.cpp +0 -0
  216. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_multi_color_merge.cpp +0 -0
  217. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_openscad_parity.cpp +0 -0
  218. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_primitives.cpp +0 -0
  219. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_profile_time.cpp +0 -0
  220. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_profiling.cpp +0 -0
  221. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_python_bindings.py +0 -0
  222. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_render_expr.cpp +0 -0
  223. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_scope_trail.cpp +0 -0
  224. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_scoping.cpp +0 -0
  225. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_surface.cpp +0 -0
  226. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_tail_calls.cpp +0 -0
  227. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_text.cpp +0 -0
  228. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_transforms.cpp +0 -0
  229. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_undef_args.cpp +0 -0
  230. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_unexpected_args.cpp +0 -0
  231. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_use.cpp +0 -0
  232. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_value.cpp +0 -0
  233. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_viewport_params.cpp +0 -0
  234. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_zip_stored.cpp +0 -0
  235. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tools/cli/CMakeLists.txt +0 -0
  236. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tools/cli/cli_lib.cpp +0 -0
  237. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tools/cli/cli_lib.hpp +0 -0
  238. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tools/cli/main.cpp +0 -0
  239. {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/uv.lock +0 -0
@@ -1970,3 +1970,40 @@ is evaluated before the clock starts. Times under 1 ms print in microseconds (`f
1970
1970
  after the body, which a tail step would not wait for.
1971
1971
  - `PROFILE:` lines go straight to `echoFn_`, not through `emitWarning`, so the ManifoldCache never
1972
1972
  stores or replays them (a replay would print a stale time as if it were current).
1973
+
1974
+ ## `alpha_wrap()`
1975
+
1976
+ `alpha_wrap(alpha, offset) { ... }` shrink-wraps its children in a closed, manifold skin
1977
+ (BelfrySCAD #941; `supported_feature("alpha-wrap")` is 1). The skin follows any concavity a ball
1978
+ of radius `alpha` can enter and bridges any gap or hole narrower than that; every point of it lies
1979
+ `offset` outside the input. Defaults are CGAL's: alpha 1/20 of the bounding-box diagonal, offset
1980
+ alpha/30. All Normal 3D children are wrapped together into one body taking the first one's colour,
1981
+ **including an open `polyhedron()`'s raw triangles** (`ColoredBody::rawMesh`) -- wrapping geometry
1982
+ too broken for Manifold is half the point. 2D children and `%`/`#` bodies pass through (2D with a
1983
+ warning). A non-positive or non-numeric `alpha`/`offset` warns and leaves the children as they were.
1984
+
1985
+ ### Implementation notes
1986
+
1987
+ - `src/builtins/alpha_wrap.cpp`, written **from the paper** (Portaneri et al., "Alpha Wrapping with
1988
+ an Offset", SIGGRAPH 2022) and CGAL's user manual -- **not** from CGAL's `Alpha_wrap_3` source,
1989
+ which is GPL and cannot be carried into this MIT project. Keep it that way: do not paste from CGAL.
1990
+ - Delaunay is our own Bowyer-Watson insertion over **Shewchuk's exact predicates**
1991
+ (`third_party/predicates/`, public domain, the original file minus its `random()` test helpers,
1992
+ which MSVC lacks). They are K&R C, so the project enables `C`; `predicates.c` is compiled with
1993
+ warnings off and must never see fast-math. `exactinit()` runs once (`std::call_once`).
1994
+ - Four far "super" vertices stand in for the infinite vertex; a cell using one is outside and never
1995
+ entered. The loose box's 8 corners are jittered by 1e-9 of the diagonal so no four are
1996
+ cospherical; Steiner points are not jittered, and a tie (`insphere == 0`) counts as no conflict.
1997
+ - Gates are ordered by the smallest empty sphere through the face (its centre on the dual Voronoi
1998
+ edge, nearest the face's circumcentre). Rule 1 finds where the Voronoi edge first meets the offset
1999
+ surface by **sphere tracing** -- the distance to the input is 1-Lipschitz, so stepping by
2000
+ `distance - offset` cannot overshoot. Rule 2 projects the circumcentre of a cell that overlaps the
2001
+ input (separating-axis tet/triangle test over a BVH) onto the offset surface.
2002
+ - The skin is built as `MeshGL64`: rounding Steiner points to float can fold the thin triangles a
2003
+ small offset produces. Input is read from each body's float `MeshGL`.
2004
+ - `checkCancel()` every 256 gates; a 5M-Steiner-point runaway guard warns and returns the children.
2005
+ - Measured: a 242-triangle bracket wraps in 5 ms at alpha 3 (362 faces) and 137 ms at alpha 0.5
2006
+ (11.9k faces); extruded text, 2,468 triangles, 0.7 s at alpha 0.5 and 4 s at alpha 0.2 (102k
2007
+ faces). Time follows OUTPUT size. Not done: CGAL's manifold post-pass for very thin features
2008
+ (no failure seen yet), and the same input scaled can differ by a near-tie or two (same shape,
2009
+ volume within 0.2%), which is why `DefaultsScaleWithTheModel` compares within tolerances.
@@ -11,7 +11,8 @@ if(NOT _oscad_pyproject MATCHES "\nversion = \"([0-9]+)\.([0-9]+)\.([0-9]+)\"")
11
11
  endif()
12
12
  set(OSCAD_EVAL_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
13
13
 
14
- project(openscad_cpp_evaluator VERSION ${OSCAD_EVAL_VERSION} LANGUAGES CXX)
14
+ # C for third_party/predicates (Shewchuk's robust predicates, used by alpha_wrap).
15
+ project(openscad_cpp_evaluator VERSION ${OSCAD_EVAL_VERSION} LANGUAGES C CXX)
15
16
 
16
17
  # On Apple Silicon, warn (don't fail -- a universal or intentionally-x86_64
17
18
  # build is legitimate) if the effective target architecture is x86_64 while
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.49.8
3
+ Version: 1.50.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.49.8"
7
+ version = "1.50.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -47,6 +47,8 @@ add_library(openscad_cpp_evaluator STATIC
47
47
  builtins/function_builtins.cpp
48
48
  builtins/import.cpp
49
49
  builtins/topology.cpp
50
+ builtins/alpha_wrap.cpp
51
+ ../third_party/predicates/predicates.c
50
52
  builtins/extrude.cpp
51
53
  builtins/roof.cpp
52
54
  builtins/surface.cpp
@@ -73,6 +75,11 @@ target_compile_options(openscad_cpp_evaluator PRIVATE
73
75
  $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall;-Wextra>
74
76
  )
75
77
 
78
+ # Shewchuk's predicates are 1996 K&R C, kept as published: quiet, and never
79
+ # with fast-math, which would break their exact arithmetic.
80
+ set_source_files_properties(../third_party/predicates/predicates.c PROPERTIES
81
+ COMPILE_OPTIONS "$<IF:$<C_COMPILER_ID:MSVC>,/w,-w>")
82
+
76
83
  # -Wall -Wextra above is about OUR code, but two TUs pull a vendored header
77
84
  # in wholesale and inherit its warnings: 27 from Boost.Polygon's voronoi
78
85
  # headers and 9 from stb_image_write's implementation, against 5 that were
@@ -0,0 +1,593 @@
1
+ // alpha_wrap(alpha, offset) -- shrink-wrap the children in a closed,
2
+ // manifold skin. A BelfrySCAD extension (BelfrySCAD #941).
3
+ //
4
+ // The skin follows any concavity a ball of radius `alpha` can enter and
5
+ // bridges any gap or hole narrower than that, and every point of it lies
6
+ // `offset` outside the input. So it is also a robust rounded outward offset,
7
+ // and a way to wrap geometry too broken for Manifold to accept: an open
8
+ // polyhedron()'s raw triangles are wrapped like any other.
9
+ //
10
+ // Written from Portaneri, Rouxel-Labbe, Hemmer, Cohen-Steiner and Alliez,
11
+ // "Alpha Wrapping with an Offset" (ACM TOG 41(4), SIGGRAPH 2022) and CGAL's
12
+ // user manual for its Alpha_wrap_3 package -- NOT from CGAL's source, which
13
+ // is GPL and could not be carried into this MIT project. The predicates are
14
+ // Shewchuk's, which are public domain (third_party/predicates).
15
+ //
16
+ // The algorithm, as the paper gives it:
17
+ // - A 3D Delaunay triangulation of a loose box around the input. Its cells
18
+ // start "inside"; the region beyond the box is "outside".
19
+ // - A gate is a face between an outside cell and an inside one. It is
20
+ // traversable when the smallest empty sphere through it -- whose centre
21
+ // lies on the Voronoi edge dual to the face -- is wider than alpha.
22
+ // Gates are taken widest first.
23
+ // - Crossing a gate into an inside cell either inserts a Steiner point on
24
+ // the offset surface (rule 1: where the Voronoi edge first meets it;
25
+ // rule 2: when the cell overlaps the input, its circumcentre projected
26
+ // onto it), whose new cells are inside, or marks the cell outside.
27
+ // - When no gate is left, the faces between inside and outside are the skin.
28
+ //
29
+ // Four far "super" vertices stand in for the triangulation's infinite
30
+ // vertex: a cell using one is "infinite", always outside, never entered.
31
+
32
+ #include "builtins.hpp"
33
+
34
+ #include "openscad_cpp_evaluator/call_args.hpp"
35
+ #include "openscad_cpp_evaluator/evaluator.hpp"
36
+
37
+ #include "../../third_party/predicates/predicates.h"
38
+
39
+ #include <algorithm>
40
+ #include <array>
41
+ #include <cmath>
42
+ #include <cstring>
43
+ #include <mutex>
44
+ #include <queue>
45
+ #include <unordered_map>
46
+ #include <vector>
47
+
48
+ namespace oscadeval {
49
+
50
+ namespace {
51
+
52
+ // CGAL's suggested defaults: alpha 1/20 of the bounding-box diagonal, and
53
+ // the offset 1/30 of alpha -- small, so sharp features survive.
54
+ constexpr double kDefaultAlphaFraction = 1.0 / 20.0;
55
+ constexpr double kDefaultOffsetFraction = 1.0 / 30.0;
56
+ // A runaway guard, not a quality knob: a tiny alpha on a big model asks for
57
+ // tens of millions of points, which would exhaust memory long before ending.
58
+ constexpr size_t kMaxSteinerPoints = 5'000'000;
59
+
60
+ using V3 = std::array<double, 3>;
61
+ V3 operator+(V3 a, V3 b) { return {a[0] + b[0], a[1] + b[1], a[2] + b[2]}; }
62
+ V3 operator-(V3 a, V3 b) { return {a[0] - b[0], a[1] - b[1], a[2] - b[2]}; }
63
+ V3 operator*(V3 a, double s) { return {a[0] * s, a[1] * s, a[2] * s}; }
64
+ double dot(V3 a, V3 b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
65
+ V3 cross(V3 a, V3 b) { return {a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]}; }
66
+ double len(V3 a) { return std::sqrt(dot(a, a)); }
67
+
68
+ struct Tri { V3 a, b, c; };
69
+
70
+ // Closest point on a triangle (Ericson, Real-Time Collision Detection 5.1.5).
71
+ V3 closestOnTri(V3 p, const Tri& t) {
72
+ V3 ab = t.b - t.a, ac = t.c - t.a, ap = p - t.a;
73
+ double d1 = dot(ab, ap), d2 = dot(ac, ap);
74
+ if (d1 <= 0 && d2 <= 0) return t.a;
75
+ V3 bp = p - t.b;
76
+ double d3 = dot(ab, bp), d4 = dot(ac, bp);
77
+ if (d3 >= 0 && d4 <= d3) return t.b;
78
+ double vc = d1 * d4 - d3 * d2;
79
+ if (vc <= 0 && d1 >= 0 && d3 <= 0) return t.a + ab * (d1 / (d1 - d3));
80
+ V3 cp = p - t.c;
81
+ double d5 = dot(ab, cp), d6 = dot(ac, cp);
82
+ if (d6 >= 0 && d5 <= d6) return t.c;
83
+ double vb = d5 * d2 - d1 * d6;
84
+ if (vb <= 0 && d2 >= 0 && d6 <= 0) return t.a + ac * (d2 / (d2 - d6));
85
+ double va = d3 * d6 - d5 * d4;
86
+ if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0)
87
+ return t.b + (t.c - t.b) * ((d4 - d3) / ((d4 - d3) + (d5 - d6)));
88
+ double den = 1.0 / (va + vb + vc);
89
+ return t.a + ab * (vb * den) + ac * (vc * den);
90
+ }
91
+
92
+ // Separating-axis test: does the tetrahedron overlap the triangle?
93
+ bool tetTriOverlap(const V3 q[4], const Tri& t) {
94
+ const V3 tp[3] = {t.a, t.b, t.c};
95
+ auto separated = [&](V3 axis) {
96
+ if (dot(axis, axis) < 1e-30) return false;
97
+ double a0 = 1e300, a1 = -1e300, b0 = 1e300, b1 = -1e300;
98
+ for (int i = 0; i < 4; ++i) { double d = dot(axis, q[i]); a0 = std::min(a0, d); a1 = std::max(a1, d); }
99
+ for (int i = 0; i < 3; ++i) { double d = dot(axis, tp[i]); b0 = std::min(b0, d); b1 = std::max(b1, d); }
100
+ return a1 < b0 || b1 < a0;
101
+ };
102
+ static const int F[4][3] = {{1, 2, 3}, {0, 2, 3}, {0, 1, 3}, {0, 1, 2}};
103
+ for (const auto& f : F)
104
+ if (separated(cross(q[f[1]] - q[f[0]], q[f[2]] - q[f[0]]))) return false;
105
+ if (separated(cross(t.b - t.a, t.c - t.a))) return false;
106
+ static const int E[6][2] = {{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}};
107
+ const V3 te[3] = {t.b - t.a, t.c - t.b, t.a - t.c};
108
+ for (const auto& e : E)
109
+ for (const auto& d : te)
110
+ if (separated(cross(q[e[1]] - q[e[0]], d))) return false;
111
+ return true;
112
+ }
113
+
114
+ // Bounding-volume hierarchy over the input triangles: nearest point, and
115
+ // whether a tetrahedron overlaps any of them.
116
+ class Oracle {
117
+ public:
118
+ explicit Oracle(std::vector<Tri> t) : tris_(std::move(t)) { build(0, static_cast<int>(tris_.size())); }
119
+
120
+ V3 lo() const { return nodes_[0].lo; }
121
+ V3 hi() const { return nodes_[0].hi; }
122
+
123
+ V3 closest(V3 p) const {
124
+ double best = 1e300;
125
+ V3 bp{};
126
+ std::vector<int> st{0};
127
+ while (!st.empty()) {
128
+ const Node& n = nodes_[st.back()];
129
+ st.pop_back();
130
+ if (boxDist2(n, p) >= best) continue;
131
+ if (n.left < 0) {
132
+ for (int k = n.first; k < n.first + n.count; ++k) {
133
+ V3 c = closestOnTri(p, tris_[k]);
134
+ double d = dot(c - p, c - p);
135
+ if (d < best) { best = d; bp = c; }
136
+ }
137
+ } else {
138
+ st.push_back(n.left);
139
+ st.push_back(n.right);
140
+ }
141
+ }
142
+ return bp;
143
+ }
144
+ double dist(V3 p) const { return len(closest(p) - p); }
145
+
146
+ bool overlapsTet(const V3 q[4]) const {
147
+ V3 lo = q[0], hi = q[0];
148
+ for (int i = 1; i < 4; ++i)
149
+ for (int k = 0; k < 3; ++k) { lo[k] = std::min(lo[k], q[i][k]); hi[k] = std::max(hi[k], q[i][k]); }
150
+ std::vector<int> st{0};
151
+ while (!st.empty()) {
152
+ const Node& n = nodes_[st.back()];
153
+ st.pop_back();
154
+ bool apart = false;
155
+ for (int k = 0; k < 3; ++k)
156
+ if (n.hi[k] < lo[k] || hi[k] < n.lo[k]) apart = true;
157
+ if (apart) continue;
158
+ if (n.left < 0) {
159
+ for (int k = n.first; k < n.first + n.count; ++k)
160
+ if (tetTriOverlap(q, tris_[k])) return true;
161
+ } else {
162
+ st.push_back(n.left);
163
+ st.push_back(n.right);
164
+ }
165
+ }
166
+ return false;
167
+ }
168
+
169
+ private:
170
+ struct Node { V3 lo, hi; int left = -1, right = -1, first = 0, count = 0; };
171
+ std::vector<Tri> tris_;
172
+ std::vector<Node> nodes_;
173
+
174
+ int build(int first, int count) {
175
+ Node n;
176
+ n.lo = {1e300, 1e300, 1e300};
177
+ n.hi = {-1e300, -1e300, -1e300};
178
+ for (int i = first; i < first + count; ++i)
179
+ for (const V3& p : {tris_[i].a, tris_[i].b, tris_[i].c})
180
+ for (int k = 0; k < 3; ++k) { n.lo[k] = std::min(n.lo[k], p[k]); n.hi[k] = std::max(n.hi[k], p[k]); }
181
+ const int id = static_cast<int>(nodes_.size());
182
+ nodes_.push_back(n);
183
+ if (count <= 4) {
184
+ nodes_[id].first = first;
185
+ nodes_[id].count = count;
186
+ return id;
187
+ }
188
+ int axis = 0;
189
+ V3 ext = n.hi - n.lo;
190
+ if (ext[1] > ext[axis]) axis = 1;
191
+ if (ext[2] > ext[axis]) axis = 2;
192
+ const int mid = first + count / 2;
193
+ std::nth_element(tris_.begin() + first, tris_.begin() + mid, tris_.begin() + first + count,
194
+ [axis](const Tri& x, const Tri& y) {
195
+ return x.a[axis] + x.b[axis] + x.c[axis] < y.a[axis] + y.b[axis] + y.c[axis];
196
+ });
197
+ const int l = build(first, mid - first);
198
+ const int r = build(mid, first + count - mid);
199
+ nodes_[id].left = l;
200
+ nodes_[id].right = r;
201
+ return id;
202
+ }
203
+ static double boxDist2(const Node& n, V3 p) {
204
+ double d = 0;
205
+ for (int k = 0; k < 3; ++k) {
206
+ double e = std::max({n.lo[k] - p[k], 0.0, p[k] - n.hi[k]});
207
+ d += e * e;
208
+ }
209
+ return d;
210
+ }
211
+ };
212
+
213
+ struct Cell {
214
+ int v[4];
215
+ int n[4]; // neighbour across the face opposite v[i]; -1: none
216
+ bool alive = true, outside = false, infinite = false;
217
+ unsigned gen = 0; // bumped when the slot is reused, so a stale gate is recognised
218
+ V3 cc{}; // circumcentre
219
+ double r2 = 0; // squared circumradius
220
+ };
221
+
222
+ class Wrapper {
223
+ public:
224
+ Wrapper(const Oracle& oracle, double alpha, double offset, Evaluator& ev)
225
+ : oracle_(oracle), alpha2_(alpha * alpha), offset_(offset), ev_(ev) {}
226
+
227
+ // False if the runaway guard stopped it; the skin is then incomplete.
228
+ bool run(V3 lo, V3 hi);
229
+ manifold::MeshGL64 skin() const;
230
+ size_t steinerCount() const { return steiner_; }
231
+
232
+ private:
233
+ struct Gate {
234
+ double prio; int cell, face; unsigned gen;
235
+ bool operator<(const Gate& o) const { return prio < o.prio; }
236
+ };
237
+
238
+ const Oracle& oracle_;
239
+ double alpha2_, offset_;
240
+ Evaluator& ev_;
241
+ std::vector<V3> pts_;
242
+ std::vector<Cell> cells_;
243
+ std::vector<int> free_;
244
+ std::priority_queue<Gate> queue_;
245
+ int hint_ = 0;
246
+ size_t steiner_ = 0;
247
+
248
+ const double* P(int i) const { return pts_[i].data(); }
249
+
250
+ void setGeometry(Cell& c) {
251
+ c.infinite = c.v[0] < 4 || c.v[1] < 4 || c.v[2] < 4 || c.v[3] < 4;
252
+ V3 a = pts_[c.v[0]], b = pts_[c.v[1]] - a, d = pts_[c.v[2]] - a, e = pts_[c.v[3]] - a;
253
+ const double den = 2 * dot(b, cross(d, e));
254
+ V3 o = (cross(d, e) * dot(b, b) + cross(e, b) * dot(d, d) + cross(b, d) * dot(e, e)) * (1.0 / den);
255
+ c.cc = a + o;
256
+ c.r2 = dot(o, o);
257
+ }
258
+
259
+ int newCell(const int v[4]) {
260
+ int id;
261
+ if (!free_.empty()) { id = free_.back(); free_.pop_back(); }
262
+ else { id = static_cast<int>(cells_.size()); cells_.emplace_back(); }
263
+ Cell& c = cells_[id];
264
+ const unsigned g = c.gen + 1;
265
+ c = Cell();
266
+ c.gen = g;
267
+ std::memcpy(c.v, v, sizeof c.v);
268
+ std::fill(std::begin(c.n), std::end(c.n), -1);
269
+ setGeometry(c);
270
+ return id;
271
+ }
272
+
273
+ // The cell containing p, by walking from the last insertion.
274
+ int locate(V3 p) const {
275
+ int t = hint_;
276
+ while (!cells_[t].alive) ++t;
277
+ for (size_t steps = 0; steps < cells_.size() + 16; ++steps) {
278
+ const Cell& c = cells_[t];
279
+ int next = -1;
280
+ for (int f = 0; f < 4 && next < 0; ++f) {
281
+ const double* q[4] = {P(c.v[0]), P(c.v[1]), P(c.v[2]), P(c.v[3])};
282
+ q[f] = p.data();
283
+ if (orient3d(q[0], q[1], q[2], q[3]) < 0 && c.n[f] >= 0) next = c.n[f];
284
+ }
285
+ if (next < 0) return t;
286
+ t = next;
287
+ }
288
+ return t;
289
+ }
290
+
291
+ // Bowyer-Watson insertion of p; returns the cells made.
292
+ std::vector<int> insert(V3 p) {
293
+ const int c0 = locate(p);
294
+ const int pi = static_cast<int>(pts_.size());
295
+ pts_.push_back(p);
296
+ std::vector<int> cavity{c0};
297
+ std::vector<char> inCavity(cells_.size(), 0);
298
+ inCavity[c0] = 1;
299
+ for (size_t k = 0; k < cavity.size(); ++k) {
300
+ const Cell& c = cells_[cavity[k]];
301
+ for (int f = 0; f < 4; ++f) {
302
+ const int nb = c.n[f];
303
+ if (nb < 0 || inCavity[nb]) continue;
304
+ const Cell& N = cells_[nb];
305
+ if (insphere(P(N.v[0]), P(N.v[1]), P(N.v[2]), P(N.v[3]), p.data()) > 0) {
306
+ inCavity[nb] = 1;
307
+ cavity.push_back(nb);
308
+ }
309
+ }
310
+ }
311
+ struct Boundary { int cell, face, outer; std::array<int, 4> v; };
312
+ std::vector<Boundary> bound;
313
+ for (int c : cavity)
314
+ for (int f = 0; f < 4; ++f) {
315
+ const int nb = cells_[c].n[f];
316
+ if (nb >= 0 && inCavity[nb]) continue;
317
+ Boundary b{c, f, nb, {}};
318
+ std::memcpy(b.v.data(), cells_[c].v, sizeof cells_[c].v);
319
+ b.v[f] = pi; // keeps the cell's orientation
320
+ bound.push_back(b);
321
+ }
322
+ for (int c : cavity) { cells_[c].alive = false; free_.push_back(c); }
323
+
324
+ std::vector<int> made;
325
+ std::unordered_map<uint64_t, std::pair<int, int>> open; // edge -> (cell, face)
326
+ for (const Boundary& b : bound) {
327
+ const int id = newCell(b.v.data());
328
+ made.push_back(id);
329
+ cells_[id].n[b.face] = b.outer;
330
+ if (b.outer >= 0) {
331
+ Cell& O = cells_[b.outer];
332
+ for (int f = 0; f < 4; ++f)
333
+ if (O.n[f] == b.cell && !std::count(b.v.begin(), b.v.end(), O.v[f])) { O.n[f] = id; break; }
334
+ }
335
+ for (int f = 0; f < 4; ++f) {
336
+ if (f == b.face) continue;
337
+ int a = -1, e = -1;
338
+ for (int i = 0; i < 4; ++i)
339
+ if (i != f && i != b.face) (a < 0 ? a : e) = cells_[id].v[i];
340
+ if (a > e) std::swap(a, e);
341
+ const uint64_t key = static_cast<uint64_t>(static_cast<uint32_t>(a)) << 32 | static_cast<uint32_t>(e);
342
+ auto it = open.find(key);
343
+ if (it == open.end()) {
344
+ open[key] = {id, f};
345
+ } else {
346
+ cells_[id].n[f] = it->second.first;
347
+ cells_[it->second.first].n[it->second.second] = id;
348
+ open.erase(it);
349
+ }
350
+ }
351
+ }
352
+ if (!made.empty()) hint_ = made[0];
353
+ return made;
354
+ }
355
+
356
+ // Squared radius of the smallest empty sphere through face f of cell c.
357
+ double minEmptyR2(int c, int f) const {
358
+ const Cell& C = cells_[c];
359
+ int idx[3], k = 0;
360
+ for (int i = 0; i < 4; ++i)
361
+ if (i != f) idx[k++] = C.v[i];
362
+ V3 a = pts_[idx[0]], b = pts_[idx[1]] - a, d = pts_[idx[2]] - a;
363
+ V3 nrm = cross(b, d);
364
+ V3 o = (cross(nrm, b) * dot(d, d) + cross(d, nrm) * dot(b, b)) * (1.0 / (2 * dot(nrm, nrm)));
365
+ const V3 fc = a + o;
366
+ const double fr2 = dot(o, o);
367
+ const int nb = C.n[f];
368
+ if (nb < 0) return fr2;
369
+ V3 c1 = C.cc, dir = cells_[nb].cc - c1;
370
+ const double s = dot(fc - c1, dir) / std::max(dot(dir, dir), 1e-300);
371
+ if (s >= 0 && s <= 1) return fr2;
372
+ return s < 0 ? C.r2 : cells_[nb].r2;
373
+ }
374
+
375
+ void pushGatesOf(int c) {
376
+ const Cell& C = cells_[c];
377
+ if (!C.outside) return;
378
+ for (int f = 0; f < 4; ++f) {
379
+ const int nb = C.n[f];
380
+ if (nb < 0 || cells_[nb].outside || cells_[nb].infinite) continue;
381
+ const double r2 = minEmptyR2(c, f);
382
+ if (r2 > alpha2_) queue_.push({r2, c, f, C.gen});
383
+ }
384
+ }
385
+
386
+ // Rule 1: the first point from a towards b at the offset distance. The
387
+ // distance is 1-Lipschitz, so stepping by (distance - offset) cannot
388
+ // overshoot the surface.
389
+ bool firstOffsetHit(V3 a, V3 b, V3& out) const {
390
+ V3 d = b - a;
391
+ const double L = len(d);
392
+ if (L <= 0) return false;
393
+ d = d * (1.0 / L);
394
+ const double eps = offset_ * 1e-6;
395
+ double s = 0;
396
+ for (int it = 0; it < 100000 && s <= L; ++it) {
397
+ const V3 p = a + d * s;
398
+ const double g = oracle_.dist(p) - offset_;
399
+ if (g <= eps) {
400
+ if (s == 0) return false; // a is itself within the offset
401
+ out = p;
402
+ return true;
403
+ }
404
+ s += std::max(g, eps);
405
+ }
406
+ return false;
407
+ }
408
+
409
+ bool steinerPoint(int from, int into, V3& sp) const {
410
+ const Cell& N = cells_[into];
411
+ if (oracle_.dist(N.cc) < offset_ && firstOffsetHit(cells_[from].cc, N.cc, sp)) return true;
412
+ V3 q[4];
413
+ for (int i = 0; i < 4; ++i) q[i] = pts_[N.v[i]];
414
+ if (oracle_.overlapsTet(q)) {
415
+ const V3 c = oracle_.closest(N.cc), dir = N.cc - c;
416
+ const double l = len(dir);
417
+ if (l < 1e-12) return false;
418
+ sp = c + dir * (offset_ / l);
419
+ return true;
420
+ }
421
+ return false;
422
+ }
423
+ };
424
+
425
+ bool Wrapper::run(V3 lo, V3 hi) {
426
+ const V3 ctr = (lo + hi) * 0.5;
427
+ const double R = len(hi - lo) * 1000;
428
+ pts_ = {ctr + V3{R, R, R}, ctr + V3{-R, -R, R}, ctr + V3{-R, R, -R}, ctr + V3{R, -R, -R}};
429
+ int v0[4] = {0, 1, 2, 3};
430
+ if (orient3d(P(0), P(1), P(2), P(3)) < 0) std::swap(v0[2], v0[3]);
431
+ newCell(v0);
432
+ // The loose box's corners, jittered a hair so no four are cospherical.
433
+ unsigned seed = 12345;
434
+ auto jitter = [&] { seed = seed * 1103515245u + 12345u; return ((seed >> 8) % 1000) * 1e-9; };
435
+ const double diag = len(hi - lo);
436
+ for (int i = 0; i < 8; ++i) {
437
+ V3 p{i & 1 ? hi[0] : lo[0], i & 2 ? hi[1] : lo[1], i & 4 ? hi[2] : lo[2]};
438
+ insert(p + V3{jitter(), jitter(), jitter()} * diag);
439
+ }
440
+ for (Cell& c : cells_)
441
+ if (c.alive) c.outside = c.infinite;
442
+ for (size_t c = 0; c < cells_.size(); ++c)
443
+ if (cells_[c].alive) pushGatesOf(static_cast<int>(c));
444
+
445
+ size_t gates = 0;
446
+ while (!queue_.empty()) {
447
+ if ((++gates & 0xFF) == 0) ev_.checkCancel();
448
+ const Gate g = queue_.top();
449
+ queue_.pop();
450
+ const Cell& C = cells_[g.cell];
451
+ if (!C.alive || C.gen != g.gen || !C.outside) continue;
452
+ const int into = C.n[g.face];
453
+ if (into < 0 || cells_[into].outside || cells_[into].infinite) continue;
454
+ V3 sp;
455
+ if (steinerPoint(g.cell, into, sp)) {
456
+ if (++steiner_ > kMaxSteinerPoints) return false;
457
+ const std::vector<int> made = insert(sp);
458
+ for (int c : made) cells_[c].outside = cells_[c].infinite; // new cells are inside
459
+ // New gates can only touch the new cells: theirs, and their
460
+ // outside neighbours' faces onto them.
461
+ for (int c : made) {
462
+ pushGatesOf(c);
463
+ for (int f = 0; f < 4; ++f) {
464
+ const int nb = cells_[c].n[f];
465
+ if (nb >= 0 && cells_[nb].outside) pushGatesOf(nb);
466
+ }
467
+ }
468
+ } else {
469
+ cells_[into].outside = true;
470
+ pushGatesOf(into);
471
+ }
472
+ }
473
+ return true;
474
+ }
475
+
476
+ // MeshGL64: the Steiner points are placed in double precision, and rounding
477
+ // them to float could fold the thin triangles a small offset produces.
478
+ manifold::MeshGL64 Wrapper::skin() const {
479
+ manifold::MeshGL64 mesh;
480
+ mesh.numProp = 3;
481
+ std::unordered_map<int, uint64_t> remap;
482
+ auto vert = [&](int v) {
483
+ auto [it, fresh] = remap.try_emplace(v, static_cast<uint64_t>(remap.size()));
484
+ if (fresh)
485
+ for (int k = 0; k < 3; ++k) mesh.vertProperties.push_back(pts_[v][k]);
486
+ return it->second;
487
+ };
488
+ for (size_t c = 0; c < cells_.size(); ++c) {
489
+ const Cell& C = cells_[c];
490
+ if (!C.alive || C.outside) continue;
491
+ for (int f = 0; f < 4; ++f) {
492
+ const int nb = C.n[f];
493
+ if (nb >= 0 && !cells_[nb].outside) continue;
494
+ // Wound so the normal points away from v[f], out of the inside cell.
495
+ int r[3], k = 0;
496
+ for (int i = 0; i < 4; ++i)
497
+ if (i != f) r[k++] = C.v[i];
498
+ if (orient3d(P(r[0]), P(r[1]), P(r[2]), P(C.v[f])) < 0) std::swap(r[1], r[2]);
499
+ for (int v : r) mesh.triVerts.push_back(vert(v));
500
+ }
501
+ }
502
+ return mesh;
503
+ }
504
+
505
+ void appendTriangles(const manifold::MeshGL& m, std::vector<Tri>& out) {
506
+ const size_t np = m.numProp;
507
+ auto at = [&](uint32_t v) {
508
+ return V3{m.vertProperties[v * np], m.vertProperties[v * np + 1], m.vertProperties[v * np + 2]};
509
+ };
510
+ for (size_t i = 0; i + 2 < m.triVerts.size(); i += 3)
511
+ out.push_back({at(m.triVerts[i]), at(m.triVerts[i + 1]), at(m.triVerts[i + 2])});
512
+ }
513
+
514
+ } // namespace
515
+
516
+ CSGParams resolveAlphaWrap(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
517
+ auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
518
+ CSGParams params;
519
+ params["alpha"] = getArg(args, 0, "alpha", Value{});
520
+ params["offset"] = getArg(args, 1, "offset", Value{});
521
+ EvalContext blockCtx = ev.blockScope(effCtx);
522
+ ev.evalChildren(node.children, blockCtx);
523
+ return params;
524
+ }
525
+
526
+ std::vector<ColoredBody> generateAlphaWrap(Evaluator& ev, const CSGParams& params,
527
+ const std::vector<std::unique_ptr<CSGNode>>& children,
528
+ const oscad::ASTNode& node) {
529
+ const std::vector<ColoredBody> bodies = flattenCsgTree(children);
530
+ if (bodies.empty()) return {};
531
+
532
+ // Background/highlight geometry and 2D sections pass through untouched;
533
+ // every other solid, including an open polyhedron()'s raw triangles, is
534
+ // wrapped together.
535
+ std::vector<Tri> tris;
536
+ std::vector<ColoredBody> out;
537
+ std::optional<std::array<float, 4>> color;
538
+ bool colorTaken = false, sawSection = false;
539
+ for (const ColoredBody& b : bodies) {
540
+ if (b.role != BodyRole::Normal || (!b.body && !b.rawMesh)) {
541
+ sawSection = sawSection || (b.role == BodyRole::Normal && b.section);
542
+ out.push_back(b);
543
+ continue;
544
+ }
545
+ const size_t before = tris.size();
546
+ if (b.rawMesh) appendTriangles(*b.rawMesh, tris);
547
+ else if (!b.body->IsEmpty()) appendTriangles(b.body->GetMeshGL(), tris);
548
+ if (!colorTaken && tris.size() > before) { color = b.color; colorTaken = true; }
549
+ }
550
+ if (sawSection) ev.warn("alpha_wrap: 2D children are not wrapped", &node.position());
551
+ if (tris.empty()) return out;
552
+
553
+ Oracle oracle(std::move(tris));
554
+ const V3 d = oracle.hi() - oracle.lo();
555
+ const double diag = len(d);
556
+
557
+ auto number = [&](const char* name, double fallback) -> std::optional<double> {
558
+ const Value& v = params.at(name);
559
+ if (std::holds_alternative<std::monostate>(v)) return fallback;
560
+ if (std::holds_alternative<double>(v) && std::get<double>(v) > 0 && std::isfinite(std::get<double>(v)))
561
+ return std::get<double>(v);
562
+ ev.warn(std::string("alpha_wrap: ") + name + " must be a positive number", &node.position());
563
+ return std::nullopt;
564
+ };
565
+ const std::optional<double> alpha = number("alpha", kDefaultAlphaFraction * diag);
566
+ if (!alpha) return bodies;
567
+ const std::optional<double> offset = number("offset", kDefaultOffsetFraction * *alpha);
568
+ if (!offset) return bodies;
569
+
570
+ static std::once_flag predicatesReady;
571
+ std::call_once(predicatesReady, [] { exactinit(); });
572
+
573
+ // The box must leave room for the skin, which sits `offset` out, and for
574
+ // a ball of radius alpha to pass all round it.
575
+ const double margin = 2 * (*alpha + *offset);
576
+ Wrapper wrap(oracle, *alpha, *offset, ev);
577
+ if (!wrap.run(oracle.lo() - V3{margin, margin, margin}, oracle.hi() + V3{margin, margin, margin})) {
578
+ ev.warn("alpha_wrap: stopped after " + std::to_string(kMaxSteinerPoints) +
579
+ " points; alpha is too small for this model",
580
+ &node.position());
581
+ return bodies;
582
+ }
583
+ manifold::Manifold body(wrap.skin());
584
+ if (body.Status() != manifold::Manifold::Error::NoError) {
585
+ ev.warn("alpha_wrap: the skin is not manifold", &node.position());
586
+ }
587
+ ColoredBody cb = ev.tagGenerated(std::move(body), node, Value{});
588
+ cb.color = color;
589
+ out.insert(out.begin(), std::move(cb));
590
+ return out;
591
+ }
592
+
593
+ } // namespace oscadeval