openscad-cpp-evaluator 0.28.0__tar.gz → 0.29.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 (199) hide show
  1. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/evaluator.hpp +5 -0
  3. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/primitives_3d.cpp +159 -8
  5. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/csg_resolve.cpp +40 -0
  6. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_booleans.cpp +133 -8
  7. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_python_bindings.py +9 -6
  8. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/.github/workflows/ci.yml +0 -0
  9. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/.github/workflows/wheels.yml +0 -0
  10. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/.gitignore +0 -0
  11. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/.gitmodules +0 -0
  12. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/CLAUDE.md +0 -0
  13. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/CMakeLists.txt +0 -0
  14. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/LICENSE +0 -0
  15. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/README.md +0 -0
  16. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/bindings/CMakeLists.txt +0 -0
  17. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/bindings/ast_to_py.cpp +0 -0
  18. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/bindings/module.cpp +0 -0
  19. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/cmake/embed_font.cmake +0 -0
  20. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/examples/CMakeLists.txt +0 -0
  21. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/examples/eval_perf_benchmark.cpp +0 -0
  22. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/examples/manifold_cache_reuse.cpp +0 -0
  23. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/examples/minimal_debugger.cpp +0 -0
  24. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  25. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/.gitignore +0 -0
  26. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  27. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/README.md +0 -0
  29. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  30. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  31. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  32. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  33. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  34. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  35. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  36. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  37. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  38. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  39. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  40. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  41. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  42. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  43. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  44. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  45. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  46. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  47. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  48. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  49. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  50. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  51. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  52. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  53. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  54. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  55. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  56. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  57. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  58. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  59. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  60. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  61. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  62. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  63. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  64. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  65. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  66. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  67. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  68. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  69. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  70. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  71. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  72. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  73. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  74. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  75. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  76. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  77. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  78. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  79. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  80. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  81. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  82. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  83. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  84. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  85. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  86. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  87. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  88. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  89. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  90. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  91. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  92. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  93. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  94. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  95. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  96. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  97. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  98. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  99. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  100. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  101. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  102. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  103. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  104. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  105. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  106. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  107. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  108. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  109. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  110. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  111. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  112. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  113. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  114. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  115. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  116. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  117. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  118. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  119. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  120. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  121. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  122. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/resources/fonts/LICENSE +0 -0
  123. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  124. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/CMakeLists.txt +0 -0
  125. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/booleans.cpp +0 -0
  126. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/builtins.hpp +0 -0
  127. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/call_args.cpp +0 -0
  128. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/color.cpp +0 -0
  129. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/control.cpp +0 -0
  130. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/extrude.cpp +0 -0
  131. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/function_builtins.cpp +0 -0
  132. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/import.cpp +0 -0
  133. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/modifiers.cpp +0 -0
  134. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/primitives_2d.cpp +0 -0
  135. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/registry.cpp +0 -0
  136. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/roof.cpp +0 -0
  137. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/segments.cpp +0 -0
  138. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/surface.cpp +0 -0
  139. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/text.cpp +0 -0
  140. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/topology.cpp +0 -0
  141. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/builtins/transforms.cpp +0 -0
  142. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/bytecode_compiler.cpp +0 -0
  143. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/bytecode_vm.cpp +0 -0
  144. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/colored_body.cpp +0 -0
  145. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/csg_generate.cpp +0 -0
  146. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/css_colors.cpp +0 -0
  147. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/debug/debug_repl.cpp +0 -0
  148. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/debug_profile.cpp +0 -0
  149. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/eval_context.cpp +0 -0
  150. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/eval_error.cpp +0 -0
  151. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/eval_use.cpp +0 -0
  152. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/export.cpp +0 -0
  153. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/expr_eval.cpp +0 -0
  154. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/import/dxf_import.cpp +0 -0
  155. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/import/mesh_import.cpp +0 -0
  156. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/import/surface_load.cpp +0 -0
  157. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/import/svg_import.cpp +0 -0
  158. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/manifold_cache.cpp +0 -0
  159. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/mesh_check.cpp +0 -0
  160. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/stmt_eval.cpp +0 -0
  161. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/text/stb_font_provider.cpp +0 -0
  162. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/text/text_metrics.cpp +0 -0
  163. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/user_calls.cpp +0 -0
  164. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/value.cpp +0 -0
  165. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/src/zip_stored.cpp +0 -0
  166. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/CMakeLists.txt +0 -0
  167. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_bytecode_compiler.cpp +0 -0
  168. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_cli.cpp +0 -0
  169. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_control_flow.cpp +0 -0
  170. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_csg_tree.cpp +0 -0
  171. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_debug_hooks.cpp +0 -0
  172. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_dxf_svg_import.cpp +0 -0
  173. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_eval_context.cpp +0 -0
  174. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_export.cpp +0 -0
  175. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_expr_eval.cpp +0 -0
  176. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_extrude_roof.cpp +0 -0
  177. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_function_builtins.cpp +0 -0
  178. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_helpers.hpp +0 -0
  179. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_import_export.cpp +0 -0
  180. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_manifold_cache.cpp +0 -0
  181. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_mesh_check.cpp +0 -0
  182. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_multi_color_merge.cpp +0 -0
  183. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_primitives.cpp +0 -0
  184. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_profiling.cpp +0 -0
  185. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_scope_trail.cpp +0 -0
  186. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_scoping.cpp +0 -0
  187. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_surface.cpp +0 -0
  188. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_tail_calls.cpp +0 -0
  189. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_text.cpp +0 -0
  190. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_transforms.cpp +0 -0
  191. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_use.cpp +0 -0
  192. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_value.cpp +0 -0
  193. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_viewport_params.cpp +0 -0
  194. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tests/test_zip_stored.cpp +0 -0
  195. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tools/cli/CMakeLists.txt +0 -0
  196. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tools/cli/cli_lib.cpp +0 -0
  197. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tools/cli/cli_lib.hpp +0 -0
  198. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/tools/cli/main.cpp +0 -0
  199. {openscad_cpp_evaluator-0.28.0 → openscad_cpp_evaluator-0.29.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.28.0
3
+ Version: 0.29.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -330,6 +330,11 @@ public:
330
330
  // free function, same reasoning as tagGenerated()/builtinChildren().
331
331
  void warn(const std::string& message, const oscad::Position* position);
332
332
 
333
+ // Apply `!`: replace the whole tree with the first show_only
334
+ // subtree, discarding its ancestors and every sibling. See its
335
+ // definition for why this cannot be done by tagging bodies.
336
+ void rerootAtShowOnly(std::vector<std::unique_ptr<CSGNode>>& tree);
337
+
333
338
  // parent_module(idx) support: walks the live module-call stack
334
339
  // (innermost last) and returns the name `idx` levels up from the
335
340
  // current module, or undef if out of range. Public because
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.28.0"
7
+ version = "0.29.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -239,6 +239,164 @@ std::vector<ColoredBody> generateCylinder(Evaluator& ev, const CSGParams& params
239
239
  // otherwise produce a NotManifold body). Mirrors
240
240
  // _resolve_polyhedron/_generate_polyhedron.
241
241
 
242
+
243
+ namespace {
244
+
245
+ // Triangulate one polyhedron face.
246
+ //
247
+ // A fan -- (v0, vi, vi+1) for every i -- is only correct for a polygon
248
+ // that is both convex and planar, and BOSL2's vnf_polyhedron() routinely
249
+ // hands over neither. The end caps of a nurbs_sheet() are 34-gons that
250
+ // are concave and 3.5 units out of plane; fanning one produced 32
251
+ // triangles covering 281% of the cap's true area with 15 of them wound
252
+ // inside out, which inflated the finished solid by 12% of its surface
253
+ // area and 5% of its volume.
254
+ //
255
+ // Ear clipping in the face's own best-fit plane instead. Newell's method
256
+ // gives a normal that stays meaningful when the points are not coplanar,
257
+ // which is what makes projecting them usable at all here.
258
+ //
259
+ // ponytail: O(n^2). Faces are a handful of points in nearly every model
260
+ // and 34 in the one that prompted this; revisit if a model ever arrives
261
+ // with thousand-sided faces.
262
+ void triangulateFace(const std::vector<std::array<double, 3>>& verts, const std::vector<size_t>& loop,
263
+ std::vector<uint32_t>& out) {
264
+ const size_t n = loop.size();
265
+ if (n < 3) return;
266
+
267
+ // Winding is reversed on the way out throughout: OpenSCAD's faces are
268
+ // clockwise seen from outside, Manifold wants counter-clockwise.
269
+ auto emit = [&out](size_t a, size_t b, size_t c) {
270
+ if (a == b || b == c || a == c) return;
271
+ out.push_back(static_cast<uint32_t>(a));
272
+ out.push_back(static_cast<uint32_t>(c));
273
+ out.push_back(static_cast<uint32_t>(b));
274
+ };
275
+ if (n == 3) {
276
+ emit(loop[0], loop[1], loop[2]);
277
+ return;
278
+ }
279
+
280
+ std::array<double, 3> nrm = {0.0, 0.0, 0.0};
281
+ for (size_t i = 0; i < n; ++i) {
282
+ const std::array<double, 3>& a = verts[loop[i]];
283
+ const std::array<double, 3>& b = verts[loop[(i + 1) % n]];
284
+ nrm[0] += (a[1] - b[1]) * (a[2] + b[2]);
285
+ nrm[1] += (a[2] - b[2]) * (a[0] + b[0]);
286
+ nrm[2] += (a[0] - b[0]) * (a[1] + b[1]);
287
+ }
288
+ const double len = std::sqrt(nrm[0] * nrm[0] + nrm[1] * nrm[1] + nrm[2] * nrm[2]);
289
+ if (!(len > 1e-12)) {
290
+ // Every point collinear, or the loop encloses no area: a fan is as
291
+ // good as anything and cannot make it worse.
292
+ for (size_t i = 1; i + 1 < n; ++i) emit(loop[0], loop[i], loop[i + 1]);
293
+ return;
294
+ }
295
+ for (double& c : nrm) c /= len;
296
+
297
+ // Any two axes spanning the plane will do; take the world axis least
298
+ // aligned with the normal so the projection never collapses.
299
+ const size_t drop = (std::abs(nrm[0]) > std::abs(nrm[1]))
300
+ ? ((std::abs(nrm[0]) > std::abs(nrm[2])) ? 0 : 2)
301
+ : ((std::abs(nrm[1]) > std::abs(nrm[2])) ? 1 : 2);
302
+ std::array<double, 3> axis = {0.0, 0.0, 0.0};
303
+ axis[(drop + 1) % 3] = 1.0;
304
+ std::array<double, 3> u = {axis[1] * nrm[2] - axis[2] * nrm[1], axis[2] * nrm[0] - axis[0] * nrm[2],
305
+ axis[0] * nrm[1] - axis[1] * nrm[0]};
306
+ const double ulen = std::sqrt(u[0] * u[0] + u[1] * u[1] + u[2] * u[2]);
307
+ if (!(ulen > 1e-12)) {
308
+ for (size_t i = 1; i + 1 < n; ++i) emit(loop[0], loop[i], loop[i + 1]);
309
+ return;
310
+ }
311
+ for (double& c : u) c /= ulen;
312
+ const std::array<double, 3> w = {nrm[1] * u[2] - nrm[2] * u[1], nrm[2] * u[0] - nrm[0] * u[2],
313
+ nrm[0] * u[1] - nrm[1] * u[0]};
314
+
315
+ std::vector<std::array<double, 2>> flat(n);
316
+ for (size_t i = 0; i < n; ++i) {
317
+ const std::array<double, 3>& p = verts[loop[i]];
318
+ flat[i] = {p[0] * u[0] + p[1] * u[1] + p[2] * u[2], p[0] * w[0] + p[1] * w[1] + p[2] * w[2]};
319
+ }
320
+
321
+ auto cross2 = [](const std::array<double, 2>& a, const std::array<double, 2>& b,
322
+ const std::array<double, 2>& c) {
323
+ return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
324
+ };
325
+ double twiceArea = 0.0;
326
+ for (size_t i = 0; i < n; ++i) {
327
+ const std::array<double, 2>& a = flat[i];
328
+ const std::array<double, 2>& b = flat[(i + 1) % n];
329
+ twiceArea += a[0] * b[1] - b[0] * a[1];
330
+ }
331
+
332
+ std::vector<size_t> idx(n);
333
+ for (size_t i = 0; i < n; ++i) idx[i] = i;
334
+ if (twiceArea < 0.0) std::reverse(idx.begin(), idx.end()); // work counter-clockwise
335
+
336
+ auto inside = [&](const std::array<double, 2>& a, const std::array<double, 2>& b,
337
+ const std::array<double, 2>& c, const std::array<double, 2>& p) {
338
+ // Strictly inside, so a vertex sitting exactly on an edge does not
339
+ // veto an otherwise good ear.
340
+ const double d1 = cross2(a, b, p), d2 = cross2(b, c, p), d3 = cross2(c, a, p);
341
+ return d1 > 1e-12 && d2 > 1e-12 && d3 > 1e-12;
342
+ };
343
+
344
+ // Take the best-shaped ear available rather than the first one found.
345
+ // Any valid ear gives a correct triangulation, but on a face that is
346
+ // not flat the choice decides how the surface folds: first-found
347
+ // clipping strung long thin triangles across the curved end caps and
348
+ // came out 29% larger in area than the reference's tessellation of the
349
+ // same polygon. Preferring fat ears tracks the surface instead.
350
+ auto squareness = [&](size_t a, size_t b, size_t c) {
351
+ // Twice the area over the sum of the squared sides -- highest for
352
+ // an equilateral triangle, near zero for a sliver.
353
+ const std::array<double, 3>& p = verts[loop[a]];
354
+ const std::array<double, 3>& q = verts[loop[b]];
355
+ const std::array<double, 3>& r = verts[loop[c]];
356
+ const std::array<double, 3> e1 = {q[0] - p[0], q[1] - p[1], q[2] - p[2]};
357
+ const std::array<double, 3> e2 = {r[0] - p[0], r[1] - p[1], r[2] - p[2]};
358
+ const std::array<double, 3> e3 = {r[0] - q[0], r[1] - q[1], r[2] - q[2]};
359
+ const std::array<double, 3> x = {e1[1] * e2[2] - e1[2] * e2[1], e1[2] * e2[0] - e1[0] * e2[2],
360
+ e1[0] * e2[1] - e1[1] * e2[0]};
361
+ const double area = std::sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
362
+ const double sides = e1[0] * e1[0] + e1[1] * e1[1] + e1[2] * e1[2] +
363
+ e2[0] * e2[0] + e2[1] * e2[1] + e2[2] * e2[2] +
364
+ e3[0] * e3[0] + e3[1] * e3[1] + e3[2] * e3[2];
365
+ return sides > 1e-18 ? area / sides : 0.0;
366
+ };
367
+
368
+ size_t guard = 0;
369
+ while (idx.size() > 3 && guard++ < n * n) {
370
+ size_t bestAt = idx.size();
371
+ double bestScore = -1.0;
372
+ for (size_t i = 0; i < idx.size(); ++i) {
373
+ const size_t pi = idx[(i + idx.size() - 1) % idx.size()];
374
+ const size_t ci = idx[i];
375
+ const size_t ni = idx[(i + 1) % idx.size()];
376
+ if (cross2(flat[pi], flat[ci], flat[ni]) <= 1e-12) continue; // reflex, not an ear
377
+ bool empty = true;
378
+ for (size_t other : idx) {
379
+ if (other == pi || other == ci || other == ni) continue;
380
+ if (inside(flat[pi], flat[ci], flat[ni], flat[other])) { empty = false; break; }
381
+ }
382
+ if (!empty) continue;
383
+ const double score = squareness(pi, ci, ni);
384
+ if (score > bestScore) { bestScore = score; bestAt = i; }
385
+ }
386
+ if (bestAt == idx.size()) break; // self-intersecting or otherwise unclippable
387
+ const size_t pi = idx[(bestAt + idx.size() - 1) % idx.size()];
388
+ const size_t ci = idx[bestAt];
389
+ const size_t ni = idx[(bestAt + 1) % idx.size()];
390
+ emit(loop[pi], loop[ci], loop[ni]);
391
+ idx.erase(idx.begin() + static_cast<long>(bestAt));
392
+ }
393
+ // Whatever is left: three points, or a remainder no ear could be found
394
+ // in. A fan over the remainder is the best available answer.
395
+ for (size_t i = 1; i + 1 < idx.size(); ++i) emit(loop[idx[0]], loop[idx[i]], loop[idx[i + 1]]);
396
+ }
397
+
398
+ } // namespace
399
+
242
400
  CSGParams resolvePolyhedron(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
243
401
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
244
402
  Value pointsArg = getArg(args, 0, "points", Value{});
@@ -292,14 +450,7 @@ CSGParams resolvePolyhedron(Evaluator& ev, const oscad::ModularCall& node, EvalC
292
450
  const size_t idx = static_cast<size_t>(toDoubleLenient(idxVal));
293
451
  remapped.push_back(idx < remap.size() ? remap[idx] : 0);
294
452
  }
295
- for (size_t i = 1; i + 1 < remapped.size(); ++i) {
296
- const size_t a = remapped[0], b = remapped[i + 1], c = remapped[i];
297
- if (a != b && b != c && a != c) {
298
- tris.push_back(static_cast<uint32_t>(a));
299
- tris.push_back(static_cast<uint32_t>(b));
300
- tris.push_back(static_cast<uint32_t>(c));
301
- }
302
- }
453
+ triangulateFace(uniqueVerts, remapped, tris);
303
454
  }
304
455
 
305
456
  std::vector<Value> vertsValues;
@@ -1,3 +1,4 @@
1
+ #include <functional>
1
2
  #include "openscad_cpp_evaluator/dispatch.hpp"
2
3
  #include "openscad_cpp_evaluator/evaluator.hpp"
3
4
 
@@ -255,6 +256,7 @@ std::vector<ColoredBody> Evaluator::evaluateImpl(const NodeList& nodes, EvalCont
255
256
  using Clock = std::chrono::steady_clock;
256
257
  const Clock::time_point resolveStart = profiling_ ? Clock::now() : Clock::time_point{};
257
258
  std::vector<std::unique_ptr<CSGNode>> tree = resolveTreeImpl(nodes, ctx);
259
+ rerootAtShowOnly(tree);
258
260
  const Clock::time_point resolveEnd = profiling_ ? Clock::now() : Clock::time_point{};
259
261
  std::vector<ColoredBody> result = generateTree(tree);
260
262
  const Clock::time_point generateEnd = profiling_ ? Clock::now() : Clock::time_point{};
@@ -289,6 +291,44 @@ std::vector<ColoredBody> Evaluator::evaluateImpl(const NodeList& nodes, EvalCont
289
291
  return result;
290
292
  }
291
293
 
294
+
295
+ // `!` does not merely tag what it marks -- it makes that subtree the whole
296
+ // model. Everything else goes: siblings, and every operation wrapped around
297
+ // it. `translate([50,0,0]) !cube(5);` puts a cube at the origin, and
298
+ // `linear_extrude(height=10) !circle(10);` leaves a 2D circle rather than a
299
+ // cylinder, because the extrude is an ancestor and ancestors are discarded.
300
+ //
301
+ // Tagging the bodies and filtering them at the end -- which is what this
302
+ // did before -- cannot express that: by the time a body carries the tag,
303
+ // every ancestor has already been applied to it.
304
+ void Evaluator::rerootAtShowOnly(std::vector<std::unique_ptr<CSGNode>>& tree) {
305
+ std::vector<std::unique_ptr<CSGNode>*> found;
306
+ // Pre-order, so `found.front()` is the first one in the source.
307
+ std::function<void(std::vector<std::unique_ptr<CSGNode>>&)> walk =
308
+ [&](std::vector<std::unique_ptr<CSGNode>>& nodes) {
309
+ for (std::unique_ptr<CSGNode>& n : nodes) {
310
+ if (!n) continue;
311
+ if (n->kind == "show_only") found.push_back(&n);
312
+ walk(n->children);
313
+ }
314
+ };
315
+ walk(tree);
316
+ if (found.empty()) return;
317
+
318
+ if (found.size() > 1) {
319
+ // Same rule as the reference: warn, and take the first.
320
+ const CSGNode& second = **found[1];
321
+ warn("More than one Root Modifier (!)",
322
+ second.node ? &second.node->position() : nullptr);
323
+ }
324
+
325
+ // Moved out before the rest of the tree is dropped -- its owner is
326
+ // somewhere inside what is about to be destroyed.
327
+ std::unique_ptr<CSGNode> chosen = std::move(*found.front());
328
+ tree.clear();
329
+ tree.push_back(std::move(chosen));
330
+ }
331
+
292
332
  std::vector<ColoredBody> Evaluator::evaluate(const std::vector<std::unique_ptr<oscad::ASTNode>>& nodes, EvalContext& ctx,
293
333
  const std::unordered_map<std::string, Value>& viewportParams) {
294
334
  return evaluateImpl(nodes, ctx, viewportParams);
@@ -177,18 +177,143 @@ TEST(Modifiers, BackgroundIsExcludedFromBooleanMergeButStillReturned) {
177
177
  EXPECT_TRUE(hasBackground);
178
178
  }
179
179
 
180
- TEST(Modifiers, TopLevelShowOnlyFiltersOutEverythingElse) {
180
+ TEST(Modifiers, TopLevelShowOnlyLeavesNothingButItsOwnSubtree) {
181
181
  Evaluated e = evaluateSrc("!cube(1); cube(2); #cube(3);");
182
- // evaluate()'s top-level filter: any show_only body present -> keep
183
- // only show_only + highlight bodies. cube(2) (role=normal) is dropped;
184
- // cube(1) (show_only) and cube(3) (highlight) survive.
185
- ASSERT_EQ(e.bodies.size(), 2u);
186
- for (const ColoredBody& b : e.bodies) {
187
- EXPECT_TRUE(b.role == BodyRole::ShowOnly || b.role == BodyRole::Highlight);
188
- }
182
+ // `!` makes its subtree the whole model, so both siblings go -- the
183
+ // highlighted one included. This test previously expected cube(3) to
184
+ // survive on the strength of its role; the reference implementation
185
+ // renders only cube(1) here (checked against 2021.01).
186
+ ASSERT_EQ(e.bodies.size(), 1u);
187
+ EXPECT_EQ(e.bodies[0].role, BodyRole::ShowOnly);
188
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 1.0, 1e-9);
189
+ }
190
+
191
+ // The reason a body-role filter could not do this: by the time a body is
192
+ // tagged, every operation wrapped around it has already been applied.
193
+ TEST(Modifiers, ShowOnlyDiscardsTheOperationsWrappedAroundIt) {
194
+ Evaluated moved = evaluateSrc("translate([50,0,0]) !cube(5);");
195
+ ASSERT_EQ(moved.bodies.size(), 1u);
196
+ const manifold::Box box = moved.bodies[0].body->BoundingBox();
197
+ EXPECT_NEAR(box.min.x, 0.0, 1e-9) << "the translate is an ancestor, so it does not apply";
198
+ EXPECT_NEAR(box.max.x, 5.0, 1e-9);
199
+ }
200
+
201
+ // The case that prompted this: an extrude is an ancestor like any other,
202
+ // so what is left is the 2D circle rather than the cylinder.
203
+ TEST(Modifiers, ShowOnlyInsideAnExtrudeLeavesTheProfile) {
204
+ Evaluated e = evaluateSrc("linear_extrude(height=10) !circle(10);");
205
+ ASSERT_EQ(e.bodies.size(), 1u);
206
+ // What survives is 2D: a section, not a solid. Extruding it is what
207
+ // the discarded ancestor would have done.
208
+ ASSERT_TRUE(e.bodies[0].section.has_value()) << "extruded into a solid";
209
+ const manifold::Rect bounds = e.bodies[0].section->Bounds();
210
+ EXPECT_NEAR(bounds.max.x, 10.0, 0.1);
211
+ EXPECT_NEAR(bounds.min.x, -10.0, 0.1);
212
+ }
213
+
214
+ TEST(Modifiers, MoreThanOneShowOnlyTakesTheFirstAndWarns) {
215
+ std::vector<std::string> echoed;
216
+ Evaluated e = evaluateSrc("translate([50,0,0]) !cube(5);\n!sphere(3);",
217
+ [&echoed](const std::string& m) { echoed.push_back(m); });
218
+ ASSERT_EQ(e.bodies.size(), 1u);
219
+ const manifold::Box box = e.bodies[0].body->BoundingBox();
220
+ EXPECT_NEAR(box.max.x, 5.0, 1e-9) << "the cube, not the sphere";
221
+ EXPECT_TRUE(std::any_of(echoed.begin(), echoed.end(), [](const std::string& m) {
222
+ return m.find("More than one Root Modifier") != std::string::npos;
223
+ })) << "no warning in: " << (echoed.empty() ? "(nothing)" : echoed.front());
189
224
  }
190
225
 
191
226
  TEST(Modifiers, NoShowOnlyMeansAllRolesPassThrough) {
192
227
  Evaluated e = evaluateSrc("%cube(1); cube(2);");
193
228
  EXPECT_EQ(e.bodies.size(), 2u);
194
229
  }
230
+
231
+ // -- polyhedron face triangulation ---------------------------------------
232
+
233
+ // An L-shaped prism: its top and bottom are 6-gons with one reflex corner.
234
+ // Fan-triangulating a concave face lays triangles outside the polygon and
235
+ // winds some of them inside out, so the solid comes out the wrong size --
236
+ // which is how BOSL2's nurbs_sheet() end caps ended up covering 281% of
237
+ // their own area with 15 of 32 triangles reversed.
238
+ // A U: vertex 0 cannot see the far arm, so a fan anchored there lays
239
+ // triangles across the notch, outside the solid. (An L is not enough on
240
+ // its own -- it happens to be star-shaped from its first vertex, so even
241
+ // a fan triangulates it correctly, which is why the volume check below
242
+ // needs this shape too.)
243
+ static const char* kUPrism = R"(
244
+ pts = [[0,0],[3,0],[3,3],[2,3],[2,1],[1,1],[1,3],[0,3]];
245
+ polyhedron(
246
+ points = concat([for (p=pts) [p[0],p[1],0]], [for (p=pts) [p[0],p[1],1]]),
247
+ faces = concat(
248
+ [[for (i=[0:len(pts)-1]) i]],
249
+ [[for (i=[len(pts)-1:-1:0]) i+len(pts)]],
250
+ [for (i=[0:len(pts)-1]) [i, i+len(pts), (i+1)%len(pts)+len(pts), (i+1)%len(pts)]]
251
+ )
252
+ );
253
+ )";
254
+
255
+ static const char* kLPrism = R"(
256
+ polyhedron(
257
+ points = [
258
+ [0,0,0], [2,0,0], [2,1,0], [1,1,0], [1,2,0], [0,2,0],
259
+ [0,0,1], [2,0,1], [2,1,1], [1,1,1], [1,2,1], [0,2,1]
260
+ ],
261
+ faces = [
262
+ [0,1,2,3,4,5],
263
+ [11,10,9,8,7,6],
264
+ [0,6,7,1], [1,7,8,2], [2,8,9,3],
265
+ [3,9,10,4], [4,10,11,5], [5,11,6,0]
266
+ ]
267
+ );
268
+ )";
269
+
270
+ TEST(PolyhedronFaces, AConcaveFaceGivesTheRightVolume) {
271
+ Evaluated e = evaluateSrc(kLPrism);
272
+ ASSERT_EQ(e.bodies.size(), 1u);
273
+ ASSERT_TRUE(e.bodies[0].body.has_value());
274
+ // The L is 3 square units, extruded 1 high.
275
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 3.0, 1e-9);
276
+ }
277
+
278
+ TEST(PolyhedronFaces, AConcaveFaceGivesTheRightSurfaceArea) {
279
+ Evaluated e = evaluateSrc(kLPrism);
280
+ ASSERT_EQ(e.bodies.size(), 1u);
281
+ // 2 x 3 for the ends, plus a perimeter of 8 x height 1.
282
+ EXPECT_NEAR(e.bodies[0].body->SurfaceArea(), 14.0, 1e-9);
283
+ }
284
+
285
+ TEST(PolyhedronFaces, EachFaceBecomesExactlyNMinusTwoTriangles) {
286
+ Evaluated e = evaluateSrc(kLPrism);
287
+ ASSERT_EQ(e.bodies.size(), 1u);
288
+ // Two 6-gons (4 each) and six quads (2 each).
289
+ const manifold::MeshGL mesh = e.bodies[0].body->GetMeshGL();
290
+ EXPECT_EQ(mesh.triVerts.size() / 3, 4u + 4u + 6u * 2u);
291
+ }
292
+
293
+ // A convex face has to keep working, and a triangle must not be disturbed.
294
+ TEST(PolyhedronFaces, AFaceWithAnUnseeableCornerGivesTheRightVolume) {
295
+ Evaluated e = evaluateSrc(kUPrism);
296
+ ASSERT_EQ(e.bodies.size(), 1u);
297
+ ASSERT_TRUE(e.bodies[0].body.has_value());
298
+ // 3x3 less the 1x2 notch, one unit high.
299
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 7.0, 1e-9);
300
+ // Two ends of 7, plus a 16-long perimeter one unit high.
301
+ EXPECT_NEAR(e.bodies[0].body->SurfaceArea(), 30.0, 1e-9);
302
+ }
303
+
304
+ TEST(PolyhedronFaces, AConvexFaceIsUnchanged) {
305
+ Evaluated e = evaluateSrc("cube(2);");
306
+ ASSERT_EQ(e.bodies.size(), 1u);
307
+ EXPECT_NEAR(e.bodies[0].body->Volume(), 8.0, 1e-9);
308
+ EXPECT_NEAR(e.bodies[0].body->SurfaceArea(), 24.0, 1e-9);
309
+ }
310
+
311
+ TEST(PolyhedronFaces, ADegenerateFaceDoesNotThrowOrHang) {
312
+ // All points collinear: no plane to project onto. It must fall through
313
+ // rather than spin in the ear-clipping loop.
314
+ Evaluated e = evaluateSrc(R"(
315
+ polyhedron(points=[[0,0,0],[1,0,0],[2,0,0],[0,0,1]],
316
+ faces=[[0,1,2],[0,2,3],[0,3,1],[1,3,2]]);
317
+ )");
318
+ EXPECT_EQ(e.bodies.size(), 1u);
319
+ }
@@ -223,13 +223,16 @@ def test_role_defaults_to_normal_and_reflects_modifiers():
223
223
  # Found in the same pass as tri_colors: bodyToDict() never exposed
224
224
  # ColoredBody.role either, crashing the renderer on any real render
225
225
  # (it reads cb.role for every body, not just modified ones).
226
+ # Without a `!` anywhere, every role passes through.
227
+ path = _write("cube(1);\n#sphere(1);\n%cylinder(h=1,r=1);\n")
228
+ bodies, _ = Evaluator().evaluate(path)
229
+ assert sorted(b.role for b in bodies) == ["background", "highlight", "normal"]
230
+
231
+ # With one, its subtree IS the model: the siblings go, highlighted ones
232
+ # included (see rerootAtShowOnly in csg_resolve.cpp).
226
233
  path = _write("cube(1);\n#sphere(1);\n%cylinder(h=1,r=1);\n!cube(2);\n")
227
- ev = Evaluator()
228
- bodies, _ = ev.evaluate(path)
229
- roles = sorted(b.role for b in bodies)
230
- # show_only (!) present anywhere -> only show_only + highlight bodies
231
- # survive evaluate()'s own filter (see anyShowOnly in csg_resolve.cpp).
232
- assert roles == ["highlight", "show_only"]
234
+ bodies, _ = Evaluator().evaluate(path)
235
+ assert [b.role for b in bodies] == ["show_only"]
233
236
 
234
237
 
235
238
  # -- parse_ast: AST snapshot ---------------------------------------------