openscad-cpp-evaluator 1.45.0__tar.gz → 1.45.2__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.0 → openscad_cpp_evaluator-1.45.2}/CLAUDE.md +4 -1
  2. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/mesh_import.hpp +3 -0
  4. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/pyproject.toml +1 -1
  5. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/roof.cpp +81 -20
  6. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/import/scene_import.cpp +14 -5
  7. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_extrude_roof.cpp +79 -2
  8. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_import_export.cpp +93 -0
  9. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/uv.lock +1 -1
  10. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/.github/workflows/ci.yml +0 -0
  11. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/.github/workflows/wheels.yml +0 -0
  12. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/.gitignore +0 -0
  13. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/.gitmodules +0 -0
  14. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/CMakeLists.txt +0 -0
  15. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/LICENSE +0 -0
  16. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/README.md +0 -0
  17. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/bindings/CMakeLists.txt +0 -0
  18. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/bindings/ast_to_py.cpp +0 -0
  19. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/bindings/module.cpp +0 -0
  20. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/cmake/embed_font.cmake +0 -0
  21. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/cmake/patch_manifold.cmake +0 -0
  22. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/examples/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/examples/eval_perf_benchmark.cpp +0 -0
  24. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/examples/manifold_cache_reuse.cpp +0 -0
  25. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/examples/minimal_debugger.cpp +0 -0
  26. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  27. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/.gitignore +0 -0
  28. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  29. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  30. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/LICENSE +0 -0
  31. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/README.md +0 -0
  32. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  33. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  34. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  35. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  36. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  37. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  38. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  39. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  40. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  41. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  43. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  44. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  45. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  46. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  47. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  48. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  49. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  50. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/api.cpp +0 -0
  51. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  52. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  53. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  54. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  55. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  56. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  57. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  58. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  59. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  60. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  61. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  62. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  63. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  64. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  65. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  66. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  67. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/position.cpp +0 -0
  68. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  69. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  70. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  71. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  72. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  73. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  74. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  75. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  76. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  77. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  78. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  79. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  80. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  81. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  82. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  83. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  84. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  85. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  86. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  87. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  88. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_profile_time.cpp +0 -0
  89. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  90. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  91. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  92. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  93. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  94. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  95. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  96. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  97. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  98. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  99. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  100. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  101. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  102. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  103. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  104. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  105. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  106. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  107. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  108. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  109. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  110. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  111. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  112. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  113. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  114. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  115. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  116. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/export.hpp +0 -0
  117. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  118. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  119. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  120. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  121. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  122. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  123. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  124. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  125. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  126. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  127. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  128. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  129. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  130. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  131. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  132. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/value.hpp +0 -0
  133. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  134. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/python/openscad_cpp_evaluator/__init__.py +0 -0
  135. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/resources/fonts/LICENSE +0 -0
  136. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  137. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  138. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  139. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  140. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/CMakeLists.txt +0 -0
  141. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/booleans.cpp +0 -0
  142. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/builtins.hpp +0 -0
  143. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/call_args.cpp +0 -0
  144. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/color.cpp +0 -0
  145. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/control.cpp +0 -0
  146. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/dxf_dim.cpp +0 -0
  147. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/extrude.cpp +0 -0
  148. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/function_builtins.cpp +0 -0
  149. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/import.cpp +0 -0
  150. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/modifiers.cpp +0 -0
  151. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/primitives_2d.cpp +0 -0
  152. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/primitives_3d.cpp +0 -0
  153. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/profile_time.cpp +0 -0
  154. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/registry.cpp +0 -0
  155. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/segments.cpp +0 -0
  156. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/surface.cpp +0 -0
  157. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/text.cpp +0 -0
  158. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/topology.cpp +0 -0
  159. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/builtins/transforms.cpp +0 -0
  160. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/bytecode_compiler.cpp +0 -0
  161. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/bytecode_vm.cpp +0 -0
  162. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/colored_body.cpp +0 -0
  163. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/coverage.cpp +0 -0
  164. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/csg_generate.cpp +0 -0
  165. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/csg_resolve.cpp +0 -0
  166. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/css_colors.cpp +0 -0
  167. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/debug/debug_repl.cpp +0 -0
  168. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/debug_profile.cpp +0 -0
  169. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/eval_context.cpp +0 -0
  170. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/eval_error.cpp +0 -0
  171. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/eval_use.cpp +0 -0
  172. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/export.cpp +0 -0
  173. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/expr_eval.cpp +0 -0
  174. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/format_closure.cpp +0 -0
  175. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/import/dxf_import.cpp +0 -0
  176. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/import/mesh_import.cpp +12 -12
  177. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/import/surface_load.cpp +0 -0
  178. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/import/svg_import.cpp +0 -0
  179. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/manifold_cache.cpp +0 -0
  180. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/measure_geometry.cpp +0 -0
  181. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/mesh_check.cpp +0 -0
  182. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/stmt_eval.cpp +0 -0
  183. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/text/font_match.cpp +0 -0
  184. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/text/freetype_font_provider.cpp +0 -0
  185. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/text/text_metrics.cpp +0 -0
  186. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/user_calls.cpp +0 -0
  187. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/utf8.cpp +0 -0
  188. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/value.cpp +0 -0
  189. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/xkcd_colors.cpp +0 -0
  190. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/xkcd_colors.hpp +0 -0
  191. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/src/zip_stored.cpp +0 -0
  192. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/CMakeLists.txt +0 -0
  193. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_booleans.cpp +0 -0
  194. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_bytecode_compiler.cpp +0 -0
  195. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_children_separate.cpp +0 -0
  196. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_cli.cpp +0 -0
  197. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_control_flow.cpp +0 -0
  198. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_coverage.cpp +0 -0
  199. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_csg_tree.cpp +0 -0
  200. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_debug_hooks.cpp +0 -0
  201. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_discretizer.cpp +0 -0
  202. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_dxf_svg_import.cpp +0 -0
  203. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_eval_context.cpp +0 -0
  204. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_export.cpp +0 -0
  205. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_expr_eval.cpp +0 -0
  206. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_feature_detection.cpp +0 -0
  207. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_function_builtins.cpp +0 -0
  208. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_helpers.hpp +0 -0
  209. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_include_cache.cpp +0 -0
  210. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_manifold_cache.cpp +0 -0
  211. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_mesh_check.cpp +0 -0
  212. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_multi_color_merge.cpp +0 -0
  213. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_openscad_parity.cpp +0 -0
  214. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_primitives.cpp +0 -0
  215. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_profile_time.cpp +0 -0
  216. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_profiling.cpp +0 -0
  217. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_python_bindings.py +0 -0
  218. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_render_expr.cpp +0 -0
  219. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_scope_trail.cpp +0 -0
  220. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_scoping.cpp +0 -0
  221. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_surface.cpp +0 -0
  222. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_tail_calls.cpp +0 -0
  223. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_text.cpp +0 -0
  224. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_transforms.cpp +0 -0
  225. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_undef_args.cpp +0 -0
  226. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_unexpected_args.cpp +0 -0
  227. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_use.cpp +0 -0
  228. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_value.cpp +0 -0
  229. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_viewport_params.cpp +0 -0
  230. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tests/test_zip_stored.cpp +0 -0
  231. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tools/cli/CMakeLists.txt +0 -0
  232. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tools/cli/cli_lib.cpp +0 -0
  233. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tools/cli/cli_lib.hpp +0 -0
  234. {openscad_cpp_evaluator-1.45.0 → openscad_cpp_evaluator-1.45.2}/tools/cli/main.cpp +0 -0
@@ -1247,7 +1247,10 @@ grep for `ponytail:`.
1247
1247
  without `-1` counts), and X3D's `IndexedTriangleSet`/`TriangleSet` are read. X3D goes through a
1248
1248
  small real XML parser (comments, CDATA, DOCTYPE, entities); VRML through a tokenizer + generic
1249
1249
  node parser that steps over `PROTO`/`EXTERNPROTO`/`ROUTE` (VRML 1.0 is refused by name). AMF's
1250
- `unit` scales to mm, and a zipped AMF (`PK` header) is read through `readStoredZipEntryBySuffix`.
1250
+ `unit` is ignored (OpenSCAD reads millimetres whatever it says), a zipped AMF (`PK` header) is read
1251
+ through `readStoredZipEntryBySuffix`, and each AMF object's vertices are welded by exact position
1252
+ (`weldVertices`, STL's), so a closed object written as a triangle soup imports closed, as in
1253
+ OpenSCAD -- per object, never across objects, and never near-coincident vertices.
1251
1254
  What is not a mesh — primitives (Box, Sphere, ...), `Inline`, AMF constellations — lands in
1252
1255
  `LoadedMesh::warnings`, which both `import()` faces emit as `import: '<file>': ...`.
1253
1256
  - `include/openscad_cpp_evaluator/import_builtin.hpp`, `include/openscad_cpp_evaluator/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.45.0
3
+ Version: 1.45.2
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT AND OFL-1.1
@@ -22,6 +22,9 @@ struct LoadedMesh {
22
22
  // "import: {what}" on parse/IO failure. Mirrors _load_stl/_load_obj/
23
23
  // _load_off/_load_3mf exactly (including STL's exact-match vertex welding
24
24
  // and OBJ/OFF's fan triangulation of >3-gon faces).
25
+ // Merges vertices at exactly equal positions (STL's corners, an AMF
26
+ // object's duplicated vertices); every position keeps its first index.
27
+ LoadedMesh weldVertices(const std::vector<std::array<double, 3>>& verts, const std::vector<std::array<int, 3>>& tris);
25
28
  LoadedMesh loadStl(const std::string& path);
26
29
  LoadedMesh loadObj(const std::string& path);
27
30
  LoadedMesh loadOff(const std::string& path);
@@ -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.0"
7
+ version = "1.45.2"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -114,10 +114,35 @@ class RoofMesh {
114
114
  }
115
115
  }
116
116
 
117
+ // A vertex no triangle uses -- the far end of an unbounded Voronoi edge
118
+ // outside the outline can lie 10^12 away -- would stretch the bounding
119
+ // box Manifold sizes its tolerance by, and its cleanup would then
120
+ // collapse the whole roof; such vertices are left out. (Only when one
121
+ // lies well beyond the rest: renumbering reorders the output, and one
122
+ // nearer changes the tolerance by no more than a few times.)
117
123
  manifold::Manifold build() {
118
124
  mesh_.numProp = 3;
125
+ const size_t n = mesh_.vertProperties.size() / 3;
126
+ std::vector<bool> used(n, false);
119
127
  for (const auto& t : triangles_) {
120
- for (int i : t) mesh_.triVerts.push_back(static_cast<uint64_t>(i));
128
+ for (int i : t) used[i] = true;
129
+ }
130
+ manifold::Box box;
131
+ for (size_t i = 0; i < n; ++i) {
132
+ if (used[i]) box.Union(manifold::vec3(mesh_.vertProperties[3 * i], mesh_.vertProperties[3 * i + 1], mesh_.vertProperties[3 * i + 2]));
133
+ }
134
+ std::vector<uint64_t> to(n);
135
+ std::vector<double> kept;
136
+ bool stray = false;
137
+ for (size_t i = 0; i < n; ++i) {
138
+ const manifold::vec3 p(mesh_.vertProperties[3 * i], mesh_.vertProperties[3 * i + 1], mesh_.vertProperties[3 * i + 2]);
139
+ stray = stray || (!used[i] && la::maxelem(la::abs(p)) > 16 * box.Scale());
140
+ to[i] = kept.size() / 3;
141
+ if (used[i]) kept.insert(kept.end(), {p.x, p.y, p.z});
142
+ }
143
+ if (stray) mesh_.vertProperties = std::move(kept);
144
+ for (const auto& t : triangles_) {
145
+ for (int i : t) mesh_.triVerts.push_back(stray ? to[i] : static_cast<uint64_t>(i));
121
146
  }
122
147
  manifold::Manifold body(mesh_);
123
148
  if (body.Status() == manifold::Manifold::Error::NoError) return body;
@@ -991,16 +1016,17 @@ void dropSpikes(std::vector<Vec2>& c) {
991
1016
  }
992
1017
  }
993
1018
 
994
- // The outline of `cs` snapped to a grid of 2^30 steps across its largest
995
- // coordinate, with repeated and straight corners dropped. Sets `toGrid`.
996
- std::vector<std::vector<Vec2>> gridContours(const manifold::CrossSection& cs, double& toGrid) {
1019
+ // The outline of `cs` snapped to a grid of 2^(bits+1) steps across its
1020
+ // largest coordinate, with repeated and straight corners dropped. Sets
1021
+ // `toGrid`.
1022
+ std::vector<std::vector<Vec2>> gridContours(const manifold::CrossSection& cs, double& toGrid, int bits = 29) {
997
1023
  const manifold::Polygons polys = cs.ToPolygons();
998
1024
  double extent = 0;
999
1025
  for (const auto& poly : polys) {
1000
1026
  for (Vec2 p : poly) extent = std::max({extent, std::abs(p.x), std::abs(p.y)});
1001
1027
  }
1002
1028
  if (!(extent > 0) || !std::isfinite(extent)) return {};
1003
- toGrid = std::ldexp(1.0, 29 - std::ilogb(extent));
1029
+ toGrid = std::ldexp(1.0, bits - std::ilogb(extent));
1004
1030
 
1005
1031
  std::vector<std::vector<Vec2>> contours;
1006
1032
  for (const auto& poly : polys) {
@@ -1016,17 +1042,6 @@ std::vector<std::vector<Vec2>> gridContours(const manifold::CrossSection& cs, do
1016
1042
  return contours;
1017
1043
  }
1018
1044
 
1019
- // The roof solid over `cs` (its floor at z = 0), discretizing curved parts
1020
- // by `fa`/`fs`.
1021
- manifold::Manifold voronoiRoof(const manifold::CrossSection& cs, double fa, double fs) {
1022
- double toGrid = 1;
1023
- std::vector<std::vector<Vec2>> contours = gridContours(cs, toGrid);
1024
- if (contours.empty()) return manifold::Manifold();
1025
- // $fa/$fs below 0.01 would ask for unbounded detail (computeRoofParams
1026
- // has already clamped them, warning).
1027
- return RoofBuilder(std::move(contours), 1 / toGrid, std::max(fa, 0.01), std::max(fs, 0.01)).build();
1028
- }
1029
-
1030
1045
  // The outline welded where it touches itself: a union can leave pieces
1031
1046
  // that share an edge, or a hole against the outline, as outlines a hairline
1032
1047
  // apart, crossing at nothing Clipper can see. Each corner within kWeld of
@@ -1062,8 +1077,11 @@ std::vector<std::vector<Vec2>> weldContours(std::vector<std::vector<Vec2>> conto
1062
1077
  }
1063
1078
  }
1064
1079
  }
1065
- // ponytail: every corner against every edge, O(n^2); a grid of edges
1066
- // when outlines of tens of thousands of corners matter.
1080
+ // Each edge looks only at the corners in its x range.
1081
+ // ponytail: still O(n^2) for an outline of long edges all spanning one
1082
+ // x range; a grid of edges if that ever matters.
1083
+ const auto byX = [](Vec2 p, Vec2 q) { return p.x != q.x ? p.x < q.x : p.y < q.y; };
1084
+ std::sort(corners.begin(), corners.end(), byX);
1067
1085
  Clipper2Lib::Paths64 paths;
1068
1086
  for (const auto& c : contours) {
1069
1087
  Clipper2Lib::Path64 path;
@@ -1074,8 +1092,10 @@ std::vector<std::vector<Vec2>> weldContours(std::vector<std::vector<Vec2>> conto
1074
1092
  if (len == 0) continue;
1075
1093
  const Vec2 lo = la::min(a, b) - kWeld, hi = la::max(a, b) + kWeld;
1076
1094
  std::vector<std::pair<double, Vec2>> on;
1077
- for (Vec2 q : corners) {
1078
- if (q.x < lo.x || q.y < lo.y || q.x > hi.x || q.y > hi.y || q == a || q == b) continue;
1095
+ for (auto it = std::lower_bound(corners.begin(), corners.end(), Vec2{lo.x, -std::numeric_limits<double>::infinity()}, byX);
1096
+ it != corners.end() && it->x <= hi.x; ++it) {
1097
+ const Vec2 q = *it;
1098
+ if (q.y < lo.y || q.y > hi.y || q == a || q == b) continue;
1079
1099
  const double t = la::dot(q - a, d) / len;
1080
1100
  if (t > 0 && t < len && std::abs(cross2(d, q - a)) / len <= kWeld) on.push_back({t, q});
1081
1101
  }
@@ -1095,6 +1115,47 @@ std::vector<std::vector<Vec2>> weldContours(std::vector<std::vector<Vec2>> conto
1095
1115
  return welded;
1096
1116
  }
1097
1117
 
1118
+ // Each contour rotated to start at its least corner, and the contours sorted:
1119
+ // equal for two outlines that differ only in where Clipper starts them.
1120
+ std::vector<std::vector<Vec2>> canonical(std::vector<std::vector<Vec2>> contours) {
1121
+ const auto less = [](Vec2 a, Vec2 b) { return a.x != b.x ? a.x < b.x : a.y < b.y; };
1122
+ for (auto& c : contours) std::rotate(c.begin(), std::min_element(c.begin(), c.end(), less), c.end());
1123
+ std::sort(contours.begin(), contours.end(), [&](const auto& a, const auto& b) { return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), less); });
1124
+ return contours;
1125
+ }
1126
+
1127
+ // The roof solid over `cs` (its floor at z = 0), discretizing curved parts
1128
+ // by `fa`/`fs`.
1129
+ //
1130
+ // Boost's Voronoi builder needs segments that meet only at their ends. A
1131
+ // union's pieces sharing an edge come back as outlines a hairline apart,
1132
+ // which on the grid overlap or cross: the diagram comes out wrong, or the
1133
+ // builder never finishes, allocating without bound. So the outline is
1134
+ // welded first, as for the straight skeleton.
1135
+ //
1136
+ // Even on valid input the builder occasionally gets a near-degenerate
1137
+ // configuration wrong -- a cell left with no finite edges, where four sites
1138
+ // are all but equidistant once the outline is rounded to the grid -- and the
1139
+ // facets built from it do not close. Rounding to a coarser grid perturbs
1140
+ // the outline differently, so the roof is retried there.
1141
+ manifold::Manifold voronoiRoof(const manifold::CrossSection& cs, double fa, double fs) {
1142
+ manifold::Manifold body;
1143
+ for (int bits = 29; bits >= 25; --bits) {
1144
+ double toGrid = 1;
1145
+ std::vector<std::vector<Vec2>> contours = gridContours(cs, toGrid, bits);
1146
+ std::vector<std::vector<Vec2>> welded = weldContours(contours);
1147
+ // An outline welding leaves as it was keeps its own order, so its
1148
+ // roof is the same, vertex for vertex, as without welding.
1149
+ if (canonical(welded) != canonical(contours)) contours = std::move(welded);
1150
+ if (contours.empty()) return manifold::Manifold();
1151
+ // $fa/$fs below 0.01 would ask for unbounded detail
1152
+ // (computeRoofParams has already clamped them, warning).
1153
+ body = RoofBuilder(std::move(contours), 1 / toGrid, std::max(fa, 0.01), std::max(fs, 0.01)).build();
1154
+ if (body.Status() == manifold::Manifold::Error::NoError && !body.IsEmpty()) break;
1155
+ }
1156
+ return body;
1157
+ }
1158
+
1098
1159
  manifold::Manifold straightRoof(const manifold::CrossSection& cs) {
1099
1160
  double toGrid = 1;
1100
1161
  std::vector<std::vector<Vec2>> contours = weldContours(gridContours(cs, toGrid));
@@ -653,13 +653,13 @@ LoadedMesh loadAmf(const std::string& path) {
653
653
  LoadedMesh out;
654
654
  for (std::string_view object : segments(xml, "object")) {
655
655
  const std::string_view verticesBlock = elementText(object, "vertices");
656
- const int base = static_cast<int>(out.verts.size());
656
+ LoadedMesh obj;
657
657
  for (std::string_view vertex : segments(verticesBlock, "vertex")) {
658
658
  const std::string_view c = elementText(vertex, "coordinates");
659
- out.verts.push_back({amfNumber(elementText(c, "x"), "x") * unit, amfNumber(elementText(c, "y"), "y") * unit,
659
+ obj.verts.push_back({amfNumber(elementText(c, "x"), "x") * unit, amfNumber(elementText(c, "y"), "y") * unit,
660
660
  amfNumber(elementText(c, "z"), "z") * unit});
661
661
  }
662
- const int count = static_cast<int>(out.verts.size()) - base;
662
+ const int count = static_cast<int>(obj.verts.size());
663
663
  for (std::string_view volume : segments(object, "volume")) {
664
664
  for (std::string_view tri : segments(volume, "triangle")) {
665
665
  std::array<int, 3> t{};
@@ -669,11 +669,20 @@ LoadedMesh loadAmf(const std::string& path) {
669
669
  if (i < 0 || i >= count)
670
670
  throw std::runtime_error("AMF: triangle vertex " + std::to_string(i) + " is past its object's " +
671
671
  std::to_string(count) + " vertices");
672
- t[k] = base + i;
672
+ t[k] = i;
673
673
  }
674
- out.tris.push_back(t);
674
+ obj.tris.push_back(t);
675
675
  }
676
676
  }
677
+ // OpenSCAD treats vertices at exactly the same position as one
678
+ // (checked, 2026.02.01): a closed object written as a triangle soup,
679
+ // or with its vertices listed twice, imports as a closed solid.
680
+ // Within each object only -- two open halves in separate objects
681
+ // stay open there too. Near-coincident vertices are not merged.
682
+ obj = weldVertices(obj.verts, obj.tris);
683
+ const int base = static_cast<int>(out.verts.size());
684
+ out.verts.insert(out.verts.end(), obj.verts.begin(), obj.verts.end());
685
+ for (const auto& t : obj.tris) out.tris.push_back({base + t[0], base + t[1], base + t[2]});
677
686
  }
678
687
  if (xml.find("<constellation") != std::string::npos) {
679
688
  out.warnings.push_back("AMF constellations are not applied: each object is imported where it was modeled");
@@ -347,8 +347,8 @@ struct RoofResult {
347
347
  double slopeError; // roof area / (floor area * sqrt 2) - 1: 0 if every face rises at 45 degrees
348
348
  };
349
349
 
350
- RoofResult straightRoof(const std::string& shape) {
351
- Evaluated e = evalSrc("roof(method=\"straight\") " + shape);
350
+ RoofResult straightRoof(const std::string& shape, const std::string& method = "straight") {
351
+ Evaluated e = evalSrc("roof(method=\"" + method + "\") " + shape);
352
352
  EXPECT_EQ(e.bodies.size(), 1u);
353
353
  if (e.bodies.empty() || !e.bodies[0].body) return {0, 0, manifold::Manifold::Error::InvalidConstruction, 1};
354
354
  const manifold::Manifold& m = *e.bodies[0].body;
@@ -463,6 +463,83 @@ TEST(Roof, StraightHandlesThousandsOfVertices) {
463
463
  EXPECT_NEAR(g.slopeError, 0, 1e-5);
464
464
  }
465
465
 
466
+ // -- method="voronoi" over unions of pieces that touch along edges. These
467
+ // all gave no roof at all before: Clipper returns the pieces as outlines a
468
+ // hairline apart, which on the grid overlap or cross, and Boost's Voronoi
469
+ // builder needs segments that meet only at their ends.
470
+
471
+ namespace {
472
+
473
+ void expectVoronoiRoof(const std::string& shape, double volume, double height, double tol) {
474
+ SCOPED_TRACE(shape);
475
+ const RoofResult r = straightRoof(shape, "voronoi");
476
+ EXPECT_EQ(r.status, manifold::Manifold::Error::NoError);
477
+ EXPECT_NEAR(r.volume, volume, tol * volume);
478
+ EXPECT_NEAR(r.height, height, tol);
479
+ }
480
+
481
+ } // namespace
482
+
483
+ // Four triangles sharing edges. The reference agrees: 23.67785 / 2.39894.
484
+ TEST(Roof, VoronoiJoinsTrianglesThatShareEdges) {
485
+ expectVoronoiRoof("union() { polygon([[2.764760357, 4.118581106], [5.901400476, 6.372929785], [8.606126287, 1.988496895]]); "
486
+ "polygon([[5.901400476, 6.372929785], [8.606126287, 1.988496895], [11.028736411, 6.50946765]]); "
487
+ "polygon([[8.606126287, 1.988496895], [2.764760357, 4.118581106], [4.975336337, 1.106200614]]); "
488
+ "polygon([[8.606126287, 1.988496895], [2.764760357, 4.118581106], [6.985603416, 6.618989998]]); }",
489
+ 23.677855, 2.39894, 1e-5);
490
+ }
491
+
492
+ // Rotated rectangles sharing (parts of) edges. OpenSCAD 2026.02.01 draws
493
+ // nothing for it; the volume is the integral of the distance to the outline
494
+ // to within the faceting of the cones (170.68 numerically).
495
+ TEST(Roof, VoronoiJoinsRotatedRectanglesThatShareEdges) {
496
+ expectVoronoiRoof("union() { polygon([[-3.369828352, 0.413896268], [-7.46233132, 4.010047378], [-11.05848243, -0.08245559], [-6.965979462, -3.6786067]]); "
497
+ "polygon([[-4.919727978, -5.476682255], [-9.012230946, -1.880531145], [-14.406457611, -8.019285597], [-10.313954643, -11.615436707]]); "
498
+ "polygon([[6.613253139, -4.732154468], [0.474498687, 0.662072197], [-4.919727978, -5.476682255], [1.219026474, -10.87090892]]); "
499
+ "polygon([[10.457580178, -4.483978539], [8.411328694, -2.685902984], [6.613253139, -4.732154468], [8.659504623, -6.530230023]]); }",
500
+ 170.76468, 4.08601, 1e-5);
501
+ }
502
+
503
+ // Here the outline as Clipper leaves it crosses itself on the grid, and the
504
+ // Voronoi builder never finished, allocating until the process was killed
505
+ // (tens of gigabytes). (The integral of the distance to the outline is
506
+ // 221.3; the cones over corners meeting head on are faceted coarsely, as by
507
+ // the reference.)
508
+ TEST(Roof, VoronoiRoofOfACrossingOutlineStaysBounded) {
509
+ expectVoronoiRoof("union() { polygon([[8.304902057, -8.407695464], [0.596294574, -9.683488249], [1.872087359, -17.392095732], [9.580694842, -16.116302947]]); "
510
+ "polygon([[14.737716755, 0.576704804], [7.029109272, -0.699087981], [8.304902057, -8.407695464], [16.01350954, -7.131902679]]); "
511
+ "polygon([[8.304902057, -8.407695464], [-3.258009167, -10.321384641], [-2.620112775, -14.175688383], [8.94279845, -12.261999205]]); "
512
+ "polygon([[2.536909139, 2.517319368], [-1.317394603, 1.879422976], [0.596294574, -9.683488249], [4.450598316, -9.045591856]]); }",
513
+ 222.74776, 3.90673, 1e-5);
514
+ }
515
+
516
+ // A staircase of triangles. Outside it, two of its edges are all but
517
+ // parallel, so their Voronoi bisectors meet some 10^12 away; that vertex,
518
+ // used by no facet, stretched the bounding box Manifold sizes its tolerance
519
+ // by, and its cleanup collapsed the whole roof to nothing. (The integral of
520
+ // the distance to the outline is 78.02.)
521
+ TEST(Roof, VoronoiRoofIgnoresFarVoronoiVertices) {
522
+ expectVoronoiRoof("union() { polygon([[-1.015413575, -0.344245093], [-1.006343725, -5.505019653], [4.154430835, -5.495949802]]); "
523
+ "polygon([[-1.015413575, -0.344245093], [4.154430835, -5.495949802], [4.145360985, -0.335175242]]); "
524
+ "polygon([[-1.006343725, -5.505019653], [4.163500685, -10.656724363], [4.154430835, -5.495949802]]); "
525
+ "polygon([[4.154430835, -5.495949802], [4.163500685, -10.656724363], [9.324275246, -10.647654512]]); "
526
+ "polygon([[4.163500685, -10.656724363], [9.333345096, -15.808429072], [9.324275246, -10.647654512]]); "
527
+ "polygon([[-6.176188135, -0.353314943], [-1.006343725, -5.505019653], [-1.015413575, -0.344245093]]); }",
528
+ 78.047622, 3.02312, 1e-5);
529
+ }
530
+
531
+ // Axis-aligned rectangles: a clean outline, but rounded to the grid four
532
+ // sites are all but equidistant at one point, and Boost's builder leaves a
533
+ // cell with no finite edges. A coarser grid rounds it differently.
534
+ // OpenSCAD 2026.02.01 draws nothing here either; the integral of the
535
+ // distance to the outline is 1.348 (the faceted cone sits a little above).
536
+ TEST(Roof, VoronoiRetriesWhereTheBuilderFails) {
537
+ expectVoronoiRoof("union() { polygon([[0.7, -0.7], [2.1, -0.7], [2.1, 0.0], [0.7, 0.0]]); "
538
+ "polygon([[2.1, -0.7], [2.8, -0.7], [2.8, 0.7], [2.1, 0.7]]); "
539
+ "polygon([[2.8, -1.4], [4.2, -1.4], [4.2, 0.7], [2.8, 0.7]]); }",
540
+ 1.3513906, 0.820101, 1e-5);
541
+ }
542
+
466
543
  TEST(Roof, UnknownMethodWarnsAndFallsBackToVoronoi) {
467
544
  std::string lastWarning;
468
545
  Evaluated e = evalSrc("roof(method=\"bogus\") square(4, center=true);", [&](const std::string& msg) { lastWarning = msg; });
@@ -4,7 +4,9 @@
4
4
 
5
5
  #include "test_helpers.hpp"
6
6
 
7
+ #include <algorithm>
7
8
  #include <filesystem>
9
+ #include <map>
8
10
  #include <fstream>
9
11
  #include <gtest/gtest.h>
10
12
  #include <manifold/manifold.h>
@@ -267,6 +269,97 @@ TEST(ImportModuleContext, ZippedAmfImports) {
267
269
  EXPECT_NEAR(e.bodies[0].body->Volume(), 1.0, 1e-9);
268
270
  }
269
271
 
272
+ namespace {
273
+
274
+ using Tri = std::array<std::array<double, 3>, 3>;
275
+
276
+ // The unit cube at (x, y, z) as 12 outward triangles, two per face, the
277
+ // faces in -z, +z, -y, +x, +y, -x order.
278
+ std::vector<Tri> cubeTris(double x, double y, double z) {
279
+ const int idx[12][3] = {{0, 3, 2}, {0, 2, 1}, {4, 5, 6}, {4, 6, 7}, {0, 1, 5}, {0, 5, 4},
280
+ {1, 2, 6}, {1, 6, 5}, {2, 3, 7}, {2, 7, 6}, {3, 0, 4}, {3, 4, 7}};
281
+ const double c[8][3] = {{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}, {0, 0, 1}, {1, 0, 1}, {1, 1, 1}, {0, 1, 1}};
282
+ std::vector<Tri> out;
283
+ for (const auto& t : idx) {
284
+ Tri tri;
285
+ for (int k = 0; k < 3; ++k) tri[k] = {c[t[k]][0] + x, c[t[k]][1] + y, c[t[k]][2] + z};
286
+ out.push_back(tri);
287
+ }
288
+ return out;
289
+ }
290
+
291
+ // One <object> per entry, every triangle carrying its own three vertex
292
+ // copies: a triangle soup, as an exporter that never shares vertices writes.
293
+ std::string soupAmf(const std::vector<std::vector<Tri>>& objects) {
294
+ std::string amf = "<?xml version=\"1.0\"?>\n<amf unit=\"millimeter\">";
295
+ for (const auto& tris : objects) {
296
+ amf += "<object id=\"1\"><mesh><vertices>";
297
+ for (const Tri& t : tris)
298
+ for (const auto& p : t)
299
+ amf += "<vertex><coordinates><x>" + std::to_string(p[0]) + "</x><y>" + std::to_string(p[1]) +
300
+ "</y><z>" + std::to_string(p[2]) + "</z></coordinates></vertex>";
301
+ amf += "</vertices><volume>";
302
+ for (size_t i = 0; i < tris.size(); ++i)
303
+ amf += "<triangle><v1>" + std::to_string(3 * i) + "</v1><v2>" + std::to_string(3 * i + 1) + "</v2><v3>" +
304
+ std::to_string(3 * i + 2) + "</v3></triangle>";
305
+ amf += "</volume></mesh></object>";
306
+ }
307
+ return amf + "</amf>";
308
+ }
309
+
310
+ } // namespace
311
+
312
+ // OpenSCAD treats an AMF object's vertices at exactly the same position as
313
+ // one vertex (checked, 2026.02.01), so a closed object written as a soup
314
+ // imports as a closed solid. Without the weld it was an open surface with
315
+ // 36 boundary edges, drawn but unable to take part in CSG.
316
+ TEST(ImportModuleContext, AmfTriangleSoupWeldsIntoAClosedSolid) {
317
+ const Imported r = importText("soup.amf", soupAmf({cubeTris(0, 0, 0)}));
318
+ EXPECT_NEAR(r.volume, 1.0, 1e-9);
319
+ EXPECT_FALSE(anyContains(r.messages, "not a closed solid")) << ::testing::PrintToString(r.messages);
320
+ }
321
+
322
+ // A 3x3x1 slab with a 1x1 hole through it (eight unit cubes around the
323
+ // middle, the faces between neighbours left out), as a soup: closed, genus 1.
324
+ TEST(ImportModuleContext, AmfSoupWithAThroughHoleWeldsClosed) {
325
+ std::map<std::vector<std::array<double, 3>>, std::vector<Tri>> faces;
326
+ std::map<std::vector<std::array<double, 3>>, int> uses;
327
+ for (int i = 0; i < 3; ++i)
328
+ for (int j = 0; j < 3; ++j) {
329
+ if (i == 1 && j == 1) continue;
330
+ const std::vector<Tri> c = cubeTris(i, j, 0);
331
+ for (size_t f = 0; f < c.size(); f += 2) {
332
+ std::vector<std::array<double, 3>> key{c[f][0], c[f][1], c[f][2], c[f + 1][0], c[f + 1][1], c[f + 1][2]};
333
+ std::sort(key.begin(), key.end());
334
+ key.erase(std::unique(key.begin(), key.end()), key.end());
335
+ faces[key] = {c[f], c[f + 1]};
336
+ ++uses[key];
337
+ }
338
+ }
339
+ std::vector<Tri> ring;
340
+ for (const auto& [key, tris] : faces)
341
+ if (uses[key] == 1) ring.insert(ring.end(), tris.begin(), tris.end());
342
+ const auto path = tempPath("ring.amf");
343
+ std::ofstream(path, std::ios::binary) << soupAmf({ring});
344
+ std::vector<std::string> messages;
345
+ Evaluated e = evalSrc("import(\"" + path.generic_string() + "\");",
346
+ [&](const std::string& m) { messages.push_back(m); });
347
+ std::filesystem::remove(path);
348
+ ASSERT_EQ(e.bodies.size(), 1u);
349
+ ASSERT_TRUE(e.bodies[0].body);
350
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 8.0, 1e-9);
351
+ EXPECT_EQ(e.bodies[0].body->Genus(), 1);
352
+ EXPECT_FALSE(anyContains(messages, "not a closed solid")) << ::testing::PrintToString(messages);
353
+ }
354
+
355
+ // The weld is per object, as in OpenSCAD: a cube split into two objects,
356
+ // each half its triangles, is two open surfaces, not one closed cube.
357
+ TEST(ImportModuleContext, AmfWeldsWithinAnObjectOnly) {
358
+ const std::vector<Tri> c = cubeTris(0, 0, 0);
359
+ const Imported r = importText("halves.amf", soupAmf({{c.begin(), c.begin() + 6}, {c.begin() + 6, c.end()}}));
360
+ EXPECT_TRUE(anyContains(r.messages, "not a closed solid")) << ::testing::PrintToString(r.messages);
361
+ }
362
+
270
363
  TEST(ImportModuleContext, UnsupportedExtensionErrors) {
271
364
  const auto path = tempPath("unsupported.xyz");
272
365
  std::ofstream(path) << "x";
@@ -55,7 +55,7 @@ wheels = [
55
55
 
56
56
  [[package]]
57
57
  name = "openscad-cpp-evaluator"
58
- version = "1.45.0"
58
+ version = "1.45.2"
59
59
  source = { editable = "." }
60
60
  dependencies = [
61
61
  { name = "numpy" },