openscad-cpp-evaluator 0.14.0__tar.gz → 0.15.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 (195) hide show
  1. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/bindings/module.cpp +9 -3
  3. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/colored_body.hpp +21 -0
  4. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/evaluator.hpp +12 -0
  5. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/booleans.cpp +15 -2
  7. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/builtins.hpp +13 -1
  8. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/import.cpp +33 -24
  9. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/primitives_3d.cpp +64 -0
  10. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/topology.cpp +2 -0
  11. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/csg_generate.cpp +23 -0
  12. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_import_export.cpp +8 -1
  13. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_primitives.cpp +92 -0
  14. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/.github/workflows/ci.yml +0 -0
  15. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/.github/workflows/wheels.yml +0 -0
  16. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/.gitignore +0 -0
  17. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/.gitmodules +0 -0
  18. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/CLAUDE.md +0 -0
  19. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/LICENSE +0 -0
  21. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/README.md +0 -0
  22. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/bindings/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/cmake/embed_font.cmake +0 -0
  24. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/examples/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/examples/eval_perf_benchmark.cpp +0 -0
  26. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/examples/manifold_cache_reuse.cpp +0 -0
  27. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/examples/minimal_debugger.cpp +0 -0
  28. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  29. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/.gitignore +0 -0
  30. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  31. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  32. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/README.md +0 -0
  33. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  34. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  35. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  36. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  37. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  38. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  39. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  40. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  41. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  42. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  43. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  44. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  45. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  46. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  47. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  48. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  49. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  50. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  51. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  52. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  53. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  54. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  55. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  56. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  57. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  58. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  59. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  60. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  61. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  62. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  63. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  64. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  65. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  66. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  67. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  68. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  69. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  70. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  71. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  72. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  73. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  74. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  75. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  76. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  77. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  78. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  79. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  80. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  81. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  82. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  83. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  84. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  85. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  86. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  87. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  88. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  89. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  90. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  91. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  92. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  93. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  94. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  95. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  96. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  97. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  98. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  99. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  100. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  101. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  102. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  103. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  104. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  105. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  106. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  107. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  108. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  109. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  110. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  111. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  112. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  113. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  114. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  115. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  116. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  117. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  118. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  119. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  120. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  121. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  122. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  123. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  124. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/resources/fonts/LICENSE +0 -0
  125. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  126. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/CMakeLists.txt +0 -0
  127. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/call_args.cpp +0 -0
  128. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/color.cpp +0 -0
  129. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/control.cpp +0 -0
  130. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/extrude.cpp +0 -0
  131. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/function_builtins.cpp +0 -0
  132. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/modifiers.cpp +0 -0
  133. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/primitives_2d.cpp +0 -0
  134. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/registry.cpp +0 -0
  135. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/roof.cpp +0 -0
  136. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/segments.cpp +0 -0
  137. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/surface.cpp +0 -0
  138. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/text.cpp +0 -0
  139. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/builtins/transforms.cpp +0 -0
  140. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/bytecode_compiler.cpp +0 -0
  141. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/bytecode_vm.cpp +0 -0
  142. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/colored_body.cpp +0 -0
  143. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/csg_resolve.cpp +0 -0
  144. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/css_colors.cpp +0 -0
  145. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/debug/debug_repl.cpp +0 -0
  146. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/debug_profile.cpp +0 -0
  147. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/eval_context.cpp +0 -0
  148. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/eval_error.cpp +0 -0
  149. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/eval_use.cpp +0 -0
  150. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/export.cpp +0 -0
  151. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/expr_eval.cpp +0 -0
  152. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/import/dxf_import.cpp +0 -0
  153. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/import/mesh_import.cpp +0 -0
  154. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/import/surface_load.cpp +0 -0
  155. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/import/svg_import.cpp +0 -0
  156. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/manifold_cache.cpp +0 -0
  157. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/stmt_eval.cpp +0 -0
  158. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/text/stb_font_provider.cpp +0 -0
  159. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/text/text_metrics.cpp +0 -0
  160. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/user_calls.cpp +0 -0
  161. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/value.cpp +0 -0
  162. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/src/zip_stored.cpp +0 -0
  163. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/CMakeLists.txt +0 -0
  164. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_booleans.cpp +0 -0
  165. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_bytecode_compiler.cpp +0 -0
  166. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_cli.cpp +0 -0
  167. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_control_flow.cpp +0 -0
  168. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_csg_tree.cpp +0 -0
  169. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_debug_hooks.cpp +0 -0
  170. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_dxf_svg_import.cpp +0 -0
  171. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_eval_context.cpp +0 -0
  172. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_export.cpp +0 -0
  173. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_expr_eval.cpp +0 -0
  174. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_extrude_roof.cpp +0 -0
  175. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_function_builtins.cpp +0 -0
  176. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_helpers.hpp +0 -0
  177. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_manifold_cache.cpp +0 -0
  178. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_multi_color_merge.cpp +0 -0
  179. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_profiling.cpp +0 -0
  180. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_python_bindings.py +0 -0
  181. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_scope_trail.cpp +0 -0
  182. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_scoping.cpp +0 -0
  183. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_surface.cpp +0 -0
  184. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_tail_calls.cpp +0 -0
  185. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/tools/cli/main.cpp +0 -0
  195. {openscad_cpp_evaluator-0.14.0 → openscad_cpp_evaluator-0.15.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.14.0
3
+ Version: 0.15.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -129,7 +129,11 @@ const char* roleToString(oscadeval::BodyRole role) {
129
129
  // tri_verts (nTri, 3), run_original_id (nRun,), run_index (nRun+1,) -- the
130
130
  // same flat 3*triangle units manifold3d's own run_index uses).
131
131
  nb::dict bodyToDict(oscadeval::ColoredBody& cb) {
132
- manifold::MeshGL mesh = cb.body->GetMeshGL();
132
+ // A display-only body's Manifold is empty by construction, so its
133
+ // GetMeshGL() would hand back nothing -- take the raw triangle soup
134
+ // that failed to build instead. Everything downstream reads plain
135
+ // arrays, so the two cases are indistinguishable from here on.
136
+ manifold::MeshGL mesh = cb.isDisplayOnly() ? *cb.rawMesh : cb.body->GetMeshGL();
133
137
  const size_t numProp = mesh.numProp;
134
138
  const size_t numVert = numProp ? mesh.vertProperties.size() / numProp : 0;
135
139
  const size_t numTri = mesh.triVerts.size() / 3;
@@ -184,7 +188,9 @@ void collectIdSpans(const oscadeval::Evaluator& ev, std::vector<IdSpan>& out) {
184
188
  nb::list bodiesToList(std::vector<oscadeval::ColoredBody>& bodies) {
185
189
  nb::list out;
186
190
  for (oscadeval::ColoredBody& cb : bodies) {
187
- if (!cb.body || cb.body->IsEmpty()) continue;
191
+ // isDisplayOnly() bodies are ALWAYS IsEmpty() -- that's what made
192
+ // an open polyhedron() silently vanish here rather than render.
193
+ if (!cb.isDisplayOnly() && (!cb.body || cb.body->IsEmpty())) continue;
188
194
  out.append(bodyToDict(cb));
189
195
  }
190
196
  return out;
@@ -210,7 +216,7 @@ nb::object csgNodeToPy(const oscadeval::CSGNode& node) {
210
216
  for (const auto& [k, v] : node.params) params[nb::str(k.c_str())] = valueToPy(v);
211
217
  nb::list bodies;
212
218
  for (const oscadeval::ColoredBody& cb : node.bodies) {
213
- if (!cb.body || cb.body->IsEmpty()) continue;
219
+ if (!cb.isDisplayOnly() && (!cb.body || cb.body->IsEmpty())) continue;
214
220
  // ColoredBody here is const in this walk (tree ownership stays with
215
221
  // the caller) -- GetMeshGL() itself is logically read-only, so a
216
222
  // const_cast here is safe/local, mirroring bodyToDict's mutating
@@ -26,6 +26,27 @@ struct ColoredBody {
26
26
  bool flatPreview = false;
27
27
  BodyRole role = BodyRole::Normal;
28
28
  std::optional<std::vector<std::array<float, 4>>> triColors; // per-triangle RGBA, multi-color CSG merges only
29
+
30
+ // Set ONLY when a mesh could not be built into a valid Manifold -- in
31
+ // practice a polyhedron() whose faces don't close the surface (it has
32
+ // boundary edges), which Manifold reports by returning an *empty* body
33
+ // with Status() == NotManifold rather than by failing. Note the status
34
+ // name is misleading: a closed mesh with reversed winding, non-manifold
35
+ // vertices or self-intersections builds fine; only an OPEN one lands
36
+ // here.
37
+ //
38
+ // Carrying the raw triangle soup alongside lets the renderer still draw
39
+ // what the script described, which is far more use for spotting the
40
+ // missing face than an empty viewport. `body` is deliberately left set
41
+ // (and empty), so every existing `cb.body->...` path keeps working.
42
+ //
43
+ // Such a body is display-only: it can never take part in a CSG
44
+ // operation, since there is no Manifold for Manifold to operate on.
45
+ // splitByRole() pulls it aside much as it does a `%` background body,
46
+ // and the CSG/hull/minkowski generators re-join it unchanged.
47
+ std::optional<manifold::MeshGL> rawMesh;
48
+
49
+ bool isDisplayOnly() const { return rawMesh.has_value(); }
29
50
  };
30
51
 
31
52
  // `EvalContext::color`/CSGParams round-trip: a resolve function reads
@@ -236,6 +236,18 @@ public:
236
236
  // comment for why that reference exists at all).
237
237
  ColoredBody tagGenerated(manifold::Manifold body, const oscad::ASTNode& node, const Value& colorValue);
238
238
 
239
+ // tagGenerated()'s counterpart for a mesh Manifold refused to build --
240
+ // an open polyhedron(). Returns a display-only ColoredBody (see
241
+ // ColoredBody::rawMesh) carrying the triangle soup as-is, so it can
242
+ // still be drawn even though no CSG operation can ever touch it.
243
+ //
244
+ // A failed Manifold has no run IDs of its own, so one is reserved here
245
+ // and written onto the raw mesh -- otherwise the body would be
246
+ // invisible to selection and to the originalID -> AST node mapping
247
+ // that drives click-to-source, which is exactly when a user most wants
248
+ // to click the broken thing and be shown the line that made it.
249
+ ColoredBody tagDisplayOnly(manifold::MeshGL mesh, const oscad::ASTNode& node, const Value& colorValue);
250
+
239
251
  // Raises an OpenSCAD-level runtime error: throws EvalError with the
240
252
  // formatted "ERROR: {msg}{TRACE lines}" message (see eval_error.hpp),
241
253
  // walking the real user function/module call stack for the TRACE lines
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.14.0"
7
+ version = "0.15.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -27,8 +27,19 @@ RoleSplit splitByRole(const std::vector<ColoredBody>& bodies) {
27
27
  for (const ColoredBody& c : bodies) {
28
28
  if (c.role == BodyRole::Background) r.background.push_back(c);
29
29
  }
30
+ // Checked before the foreground sweep below and excluded from it: a
31
+ // display-only body has an empty Manifold, so letting it reach a
32
+ // boolean would silently zero the whole operation (the same failure
33
+ // mode invalid operands caused before they were filtered out).
30
34
  for (const ColoredBody& c : bodies) {
31
- if (c.role != BodyRole::Background && c.role != BodyRole::ShowOnly) r.foreground.push_back(c);
35
+ if (c.isDisplayOnly() && c.role != BodyRole::Background && c.role != BodyRole::ShowOnly) {
36
+ r.displayOnly.push_back(c);
37
+ }
38
+ }
39
+ for (const ColoredBody& c : bodies) {
40
+ if (c.role != BodyRole::Background && c.role != BodyRole::ShowOnly && !c.isDisplayOnly()) {
41
+ r.foreground.push_back(c);
42
+ }
32
43
  }
33
44
  for (const ColoredBody& c : r.foreground) {
34
45
  if (c.role == BodyRole::Highlight) r.highlight.push_back(c);
@@ -160,7 +171,7 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params, con
160
171
  const std::string& op = std::get<std::string>(params.at("op"));
161
172
  const auto& groupSizes = std::get<ListPtr>(params.at("group_sizes"))->items;
162
173
 
163
- std::vector<ColoredBody> allBg, allHi, allSo;
174
+ std::vector<ColoredBody> allBg, allHi, allSo, allDo;
164
175
  std::optional<ColoredBody> csgResult;
165
176
  size_t idx = 0;
166
177
 
@@ -173,6 +184,7 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params, con
173
184
  allBg.insert(allBg.end(), split.background.begin(), split.background.end());
174
185
  allHi.insert(allHi.end(), split.highlight.begin(), split.highlight.end());
175
186
  allSo.insert(allSo.end(), split.showOnly.begin(), split.showOnly.end());
187
+ allDo.insert(allDo.end(), split.displayOnly.begin(), split.displayOnly.end());
176
188
 
177
189
  std::vector<ColoredBody> bodies3d, sections2d;
178
190
  for (const ColoredBody& c : split.foreground) {
@@ -251,6 +263,7 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params, con
251
263
  result.insert(result.end(), allBg.begin(), allBg.end());
252
264
  result.insert(result.end(), allHi.begin(), allHi.end());
253
265
  result.insert(result.end(), allSo.begin(), allSo.end());
266
+ result.insert(result.end(), allDo.begin(), allDo.end());
254
267
  return result;
255
268
  }
256
269
 
@@ -77,11 +77,23 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params,
77
77
  // splitting/2D-merge logic.
78
78
  struct RoleSplit {
79
79
  std::vector<ColoredBody> background;
80
- std::vector<ColoredBody> foreground; // role != Background && role != ShowOnly
80
+ std::vector<ColoredBody> foreground; // role != Background && role != ShowOnly, and not display-only
81
81
  std::vector<ColoredBody> highlight; // the subset of `foreground` that's role == Highlight
82
82
  std::vector<ColoredBody> showOnly;
83
+ // Bodies with no Manifold to operate on (an open polyhedron -- see
84
+ // ColoredBody::rawMesh). Split out for the same reason showOnly is:
85
+ // they must survive a CSG/hull/minkowski step untouched rather than be
86
+ // merged into it, since there is nothing to merge. Kept separate from
87
+ // `background` because they render as ordinary geometry, not as a
88
+ // translucent ghost.
89
+ std::vector<ColoredBody> displayOnly;
83
90
  };
84
91
  RoleSplit splitByRole(const std::vector<ColoredBody>& bodies);
92
+
93
+ // manifold::ToString(Manifold::Error) only exists under MANIFOLD_DEBUG --
94
+ // not enabled in this build -- so this mirrors it. Shared by import.cpp and
95
+ // primitives_3d.cpp, both of which report why a mesh wouldn't build.
96
+ std::string manifoldErrorName(manifold::Manifold::Error e);
85
97
  std::optional<manifold::CrossSection> toCrossSection(const std::vector<ColoredBody>& bodies);
86
98
 
87
99
  // Merges a statement's bodies into one (3D union, else 2D union, else an
@@ -49,29 +49,6 @@ LoadedMesh loadMeshByExt(const std::string& path, const std::string& ext) {
49
49
 
50
50
  bool isMeshExt(const std::string& ext) { return ext == ".stl" || ext == ".obj" || ext == ".off" || ext == ".3mf"; }
51
51
 
52
- // manifold::ToString(Manifold::Error) only exists under MANIFOLD_DEBUG --
53
- // not enabled in this project's build -- so this mirrors it locally for the
54
- // not-manifold warning message.
55
- std::string manifoldErrorName(manifold::Manifold::Error e) {
56
- switch (e) {
57
- case manifold::Manifold::Error::NoError: return "NoError";
58
- case manifold::Manifold::Error::NonFiniteVertex: return "NonFiniteVertex";
59
- case manifold::Manifold::Error::NotManifold: return "NotManifold";
60
- case manifold::Manifold::Error::VertexOutOfBounds: return "VertexOutOfBounds";
61
- case manifold::Manifold::Error::PropertiesWrongLength: return "PropertiesWrongLength";
62
- case manifold::Manifold::Error::MissingPositionProperties: return "MissingPositionProperties";
63
- case manifold::Manifold::Error::MergeVectorsDifferentLengths: return "MergeVectorsDifferentLengths";
64
- case manifold::Manifold::Error::MergeIndexOutOfBounds: return "MergeIndexOutOfBounds";
65
- case manifold::Manifold::Error::TransformWrongLength: return "TransformWrongLength";
66
- case manifold::Manifold::Error::RunIndexWrongLength: return "RunIndexWrongLength";
67
- case manifold::Manifold::Error::FaceIDWrongLength: return "FaceIDWrongLength";
68
- case manifold::Manifold::Error::InvalidConstruction: return "InvalidConstruction";
69
- case manifold::Manifold::Error::ResultTooLarge: return "ResultTooLarge";
70
- case manifold::Manifold::Error::InvalidTangents: return "InvalidTangents";
71
- case manifold::Manifold::Error::Cancelled: return "Cancelled";
72
- }
73
- return "Unknown";
74
- }
75
52
 
76
53
  Value jsonToValue(const nlohmann::ordered_json& j) {
77
54
  if (j.is_boolean()) return Value{j.get<bool>()};
@@ -135,6 +112,30 @@ Value meshToVnf(const LoadedMesh& mesh) {
135
112
 
136
113
  } // namespace
137
114
 
115
+ // manifold::ToString(Manifold::Error) only exists under MANIFOLD_DEBUG --
116
+ // not enabled in this project's build -- so this mirrors it locally for the
117
+ // not-manifold warning message.
118
+ std::string manifoldErrorName(manifold::Manifold::Error e) {
119
+ switch (e) {
120
+ case manifold::Manifold::Error::NoError: return "NoError";
121
+ case manifold::Manifold::Error::NonFiniteVertex: return "NonFiniteVertex";
122
+ case manifold::Manifold::Error::NotManifold: return "NotManifold";
123
+ case manifold::Manifold::Error::VertexOutOfBounds: return "VertexOutOfBounds";
124
+ case manifold::Manifold::Error::PropertiesWrongLength: return "PropertiesWrongLength";
125
+ case manifold::Manifold::Error::MissingPositionProperties: return "MissingPositionProperties";
126
+ case manifold::Manifold::Error::MergeVectorsDifferentLengths: return "MergeVectorsDifferentLengths";
127
+ case manifold::Manifold::Error::MergeIndexOutOfBounds: return "MergeIndexOutOfBounds";
128
+ case manifold::Manifold::Error::TransformWrongLength: return "TransformWrongLength";
129
+ case manifold::Manifold::Error::RunIndexWrongLength: return "RunIndexWrongLength";
130
+ case manifold::Manifold::Error::FaceIDWrongLength: return "FaceIDWrongLength";
131
+ case manifold::Manifold::Error::InvalidConstruction: return "InvalidConstruction";
132
+ case manifold::Manifold::Error::ResultTooLarge: return "ResultTooLarge";
133
+ case manifold::Manifold::Error::InvalidTangents: return "InvalidTangents";
134
+ case manifold::Manifold::Error::Cancelled: return "Cancelled";
135
+ }
136
+ return "Unknown";
137
+ }
138
+
138
139
  namespace {
139
140
 
140
141
  Value contoursToValue(const std::vector<Contour2d>& contours) {
@@ -269,7 +270,15 @@ std::vector<ColoredBody> generateImport(Evaluator& ev, const CSGParams& params,
269
270
 
270
271
  manifold::Manifold body(mesh);
271
272
  if (body.Status() != manifold::Manifold::Error::NoError) {
272
- ev.warn("import: mesh is not manifold (" + manifoldErrorName(body.Status()) + ")", &node.position());
273
+ // Same treatment as an open polyhedron() (see generatePolyhedron):
274
+ // keep the triangles so the file can still be LOOKED at. Previously
275
+ // this warned and then handed back an empty body, which was dropped
276
+ // downstream -- so a broken STL warned once and showed nothing,
277
+ // giving no way to see what was actually wrong with it.
278
+ ev.warn("import: mesh is not a closed solid (" + manifoldErrorName(body.Status()) +
279
+ "); drawing it as a surface, but it cannot take part in any CSG operation",
280
+ &node.position());
281
+ return {ev.tagDisplayOnly(std::move(mesh), node, params.at("color"))};
273
282
  }
274
283
  return {ev.tagGenerated(std::move(body), node, params.at("color"))};
275
284
  }
@@ -14,6 +14,45 @@ namespace oscadeval {
14
14
 
15
15
  namespace {
16
16
  bool isUndef(const Value& v) { return std::holds_alternative<std::monostate>(v); }
17
+
18
+ // Edges used by an odd number of triangles -- i.e. the mesh's open border.
19
+ // A closed solid has none; every edge is shared by exactly two faces.
20
+ //
21
+ // Counted on the UNDIRECTED edge (sorted endpoints) rather than the
22
+ // directed half-edge, so a hole reads as a hole regardless of winding:
23
+ // counting directed edges would also flag a merely back-to-front face,
24
+ // which builds into a perfectly valid solid and is not what this reports.
25
+ // Whether a mesh Manifold rejected is nonetheless safe to hand to a
26
+ // renderer. Only NotManifold qualifies: it means "the triangles are fine,
27
+ // they just don't close a solid", which is exactly the open-surface case
28
+ // worth drawing. Every other status (NonFiniteVertex above all, but also
29
+ // VertexOutOfBounds and friends) means the vertex data itself is broken --
30
+ // drawing NaN coordinates would poison the scene bounding box and send the
31
+ // camera auto-fit to infinity, which is worse than showing nothing. The
32
+ // explicit finite check is belt-and-braces, since nothing documents that a
33
+ // NotManifold mesh can't ALSO contain a NaN.
34
+ bool isDrawableFailure(const manifold::Manifold& body, const manifold::MeshGL& mesh) {
35
+ if (body.Status() != manifold::Manifold::Error::NotManifold) return false;
36
+ if (mesh.triVerts.empty()) return false;
37
+ return std::all_of(mesh.vertProperties.begin(), mesh.vertProperties.end(),
38
+ [](float v) { return std::isfinite(v); });
39
+ }
40
+
41
+ size_t countBoundaryEdges(const manifold::MeshGL& mesh) {
42
+ std::map<std::pair<uint32_t, uint32_t>, int> uses;
43
+ for (size_t i = 0; i + 2 < mesh.triVerts.size(); i += 3) {
44
+ const uint32_t v[3] = {mesh.triVerts[i], mesh.triVerts[i + 1], mesh.triVerts[i + 2]};
45
+ for (int e = 0; e < 3; ++e) {
46
+ const uint32_t a = v[e], b = v[(e + 1) % 3];
47
+ ++uses[{std::min(a, b), std::max(a, b)}];
48
+ }
49
+ }
50
+ size_t open = 0;
51
+ for (const auto& [edge, count] : uses) {
52
+ if (count % 2 != 0) ++open;
53
+ }
54
+ return open;
55
+ }
17
56
  } // namespace
18
57
 
19
58
  // cube(size = 1, center = false) -- mirrors _resolve_cube/_generate_cube.
@@ -292,6 +331,31 @@ std::vector<ColoredBody> generatePolyhedron(Evaluator& ev, const CSGParams& para
292
331
  mesh.triVerts.push_back(static_cast<uint32_t>(std::get<double>(t)));
293
332
  }
294
333
  manifold::Manifold body(mesh);
334
+ if (isDrawableFailure(body, mesh)) {
335
+ // An open surface -- faces that don't close the solid. Manifold
336
+ // signals that by returning an EMPTY body rather than throwing, so
337
+ // without this branch the polyhedron simply vanishes from the
338
+ // render with nothing said, leaving the author to guess which face
339
+ // they forgot.
340
+ //
341
+ // Report the boundary-edge count rather than Manifold's own
342
+ // "NotManifold" status string, which is actively misleading here: a
343
+ // closed mesh with reversed winding, non-manifold vertices or
344
+ // self-intersections builds perfectly well, and only an OPEN one
345
+ // reaches this branch. The count points straight at the problem.
346
+ ev.warn("polyhedron: mesh is not closed -- " + std::to_string(countBoundaryEdges(mesh)) +
347
+ " boundary edge(s); drawing it as a surface, but it cannot "
348
+ "take part in any CSG operation",
349
+ &node.position());
350
+ return {ev.tagDisplayOnly(std::move(mesh), node, params.at("color"))};
351
+ }
352
+ if (body.Status() != manifold::Manifold::Error::NoError) {
353
+ // Broken vertex data rather than merely-open topology (a NaN
354
+ // coordinate, an out-of-range index). Not drawable, so this keeps
355
+ // the old drop-it behaviour -- but says so, which it never used to.
356
+ ev.warn(std::string("polyhedron: ") + manifoldErrorName(body.Status()) + "; geometry discarded",
357
+ &node.position());
358
+ }
295
359
  return {ev.tagGenerated(std::move(body), node, params.at("color"))};
296
360
  }
297
361
 
@@ -61,6 +61,7 @@ std::vector<ColoredBody> generateHull(Evaluator&, const CSGParams&, const std::v
61
61
  result.insert(result.end(), split.background.begin(), split.background.end());
62
62
  result.insert(result.end(), split.highlight.begin(), split.highlight.end());
63
63
  result.insert(result.end(), split.showOnly.begin(), split.showOnly.end());
64
+ result.insert(result.end(), split.displayOnly.begin(), split.displayOnly.end());
64
65
  return result;
65
66
  }
66
67
 
@@ -89,6 +90,7 @@ std::vector<ColoredBody> generateMinkowski(Evaluator& ev, const CSGParams&, cons
89
90
  passthrough.insert(passthrough.end(), split.background.begin(), split.background.end());
90
91
  passthrough.insert(passthrough.end(), split.highlight.begin(), split.highlight.end());
91
92
  passthrough.insert(passthrough.end(), split.showOnly.begin(), split.showOnly.end());
93
+ passthrough.insert(passthrough.end(), split.displayOnly.begin(), split.displayOnly.end());
92
94
 
93
95
  if (bodies3d.empty()) return passthrough;
94
96
  if (bodies3d.size() == 1) {
@@ -110,4 +110,27 @@ ColoredBody Evaluator::tagGenerated(manifold::Manifold body, const oscad::ASTNod
110
110
  return cb;
111
111
  }
112
112
 
113
+ ColoredBody Evaluator::tagDisplayOnly(manifold::MeshGL mesh, const oscad::ASTNode& node, const Value& colorValue) {
114
+ const std::optional<std::array<float, 4>> color = valueToColor(colorValue);
115
+
116
+ // A Manifold that failed to build carries no run IDs, so reserve one
117
+ // and describe the whole soup as a single run. ReserveIDs() draws from
118
+ // the same global counter Manifold hands to real bodies, so an ID
119
+ // minted here can never collide with one of theirs.
120
+ const uint32_t originalId = manifold::Manifold::ReserveIDs(1);
121
+ mesh.runOriginalID = {originalId};
122
+ mesh.runIndex = {0, static_cast<uint32_t>(mesh.triVerts.size())};
123
+ idToNode[originalId] = &node;
124
+ idToColor[originalId] = color;
125
+
126
+ ColoredBody cb;
127
+ // Left set-but-empty rather than nullopt: consumers dereference
128
+ // cb.body unconditionally, and an empty Manifold is also the honest
129
+ // answer to "what solid is this?" -- there isn't one.
130
+ cb.body = manifold::Manifold();
131
+ cb.color = color;
132
+ cb.rawMesh = std::move(mesh);
133
+ return cb;
134
+ }
135
+
113
136
  } // namespace oscadeval
@@ -137,7 +137,14 @@ TEST(ImportModuleContext, NonManifoldMeshWarns) {
137
137
  }
138
138
  std::string lastWarning;
139
139
  Evaluated e = evalSrc("import(\"" + path.string() + "\");", [&](const std::string& msg) { lastWarning = msg; });
140
- EXPECT_NE(lastWarning.find("import: mesh is not manifold"), std::string::npos);
140
+ EXPECT_NE(lastWarning.find("import: mesh is not a closed solid"), std::string::npos);
141
+ // The triangle is now handed back for display rather than dropped: a
142
+ // file that warns once and then shows nothing gives no way to see what
143
+ // is actually wrong with it. It carries no Manifold, so it still can't
144
+ // take part in a CSG operation.
145
+ ASSERT_EQ(e.bodies.size(), 1u);
146
+ EXPECT_TRUE(e.bodies[0].isDisplayOnly());
147
+ EXPECT_EQ(e.bodies[0].rawMesh->triVerts.size(), 3u);
141
148
  std::filesystem::remove(path);
142
149
  }
143
150
 
@@ -181,3 +181,95 @@ TEST(Primitives2d, PolygonWithPathsSelectsIndexedContour) {
181
181
  Evaluated e = evalSrc("polygon(points=[[0,0],[2,0],[2,2],[0,2]], paths=[[0,1,2,3]]);");
182
182
  EXPECT_NEAR(e.bodies[0].section->Area(), 4.0, 1e-9);
183
183
  }
184
+
185
+ // -- open (non-closed) polyhedron: display-only ---------------------------
186
+
187
+ namespace {
188
+ // A cube with the -x face omitted: five quads, so four boundary edges.
189
+ constexpr const char* kOpenCube =
190
+ "polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0],"
191
+ " [0,0,10],[10,0,10],[10,10,10],[0,10,10]],"
192
+ " faces=[[0,1,2,3],[4,7,6,5],[0,4,5,1],[1,5,6,2],[2,6,7,3]]);";
193
+ } // namespace
194
+
195
+ // Manifold cannot represent an open surface, but silently rendering nothing
196
+ // left no way to tell a broken polyhedron from a missing one. The triangles
197
+ // are kept for display instead, tagged so nothing tries to CSG them.
198
+ TEST(Polyhedron, OpenMeshIsKeptForDisplayAndWarns) {
199
+ std::string warning;
200
+ Evaluated e = evalSrc(kOpenCube, [&](const std::string& m) { warning = m; });
201
+
202
+ ASSERT_EQ(e.bodies.size(), 1u);
203
+ EXPECT_TRUE(e.bodies[0].isDisplayOnly());
204
+ EXPECT_TRUE(e.bodies[0].body->IsEmpty()); // no solid, by definition
205
+ EXPECT_EQ(e.bodies[0].rawMesh->triVerts.size(), 10u * 3u); // 5 quads -> 10 tris
206
+
207
+ EXPECT_NE(warning.find("not closed"), std::string::npos) << warning;
208
+ EXPECT_NE(warning.find("4 boundary edge"), std::string::npos) << warning;
209
+ }
210
+
211
+ // The originalID is what maps a picked triangle back to its source line, so
212
+ // a display-only body has to carry one or clicking it would select nothing
213
+ // -- precisely when a user most wants to be shown the offending code.
214
+ TEST(Polyhedron, OpenMeshCarriesAnOriginalIdForPicking) {
215
+ Evaluated e = evalSrc(kOpenCube);
216
+ ASSERT_EQ(e.bodies.size(), 1u);
217
+ const manifold::MeshGL& mesh = *e.bodies[0].rawMesh;
218
+ ASSERT_EQ(mesh.runOriginalID.size(), 1u);
219
+ EXPECT_EQ(e.ev.idToNode.count(mesh.runOriginalID[0]), 1u);
220
+ ASSERT_EQ(mesh.runIndex.size(), 2u);
221
+ EXPECT_EQ(mesh.runIndex[1], mesh.triVerts.size());
222
+ }
223
+
224
+ // A closed mesh is still a solid however badly it's wound or built -- only
225
+ // an OPEN one takes the display-only path. Manifold's own status name for
226
+ // that case ("NotManifold") reads as though it covers these too; it doesn't.
227
+ TEST(Polyhedron, ClosedButUglyMeshesStillBuildSolids) {
228
+ // All faces reversed (inside-out).
229
+ Evaluated inverted = evalSrc(
230
+ "polyhedron(points=[[0,0,0],[10,0,0],[10,10,0],[0,10,0],"
231
+ " [0,0,10],[10,0,10],[10,10,10],[0,10,10]],"
232
+ " faces=[[3,2,1,0],[5,6,7,4],[1,5,4,0],[2,6,5,1],[3,7,6,2],[0,4,7,3]]);");
233
+ ASSERT_EQ(inverted.bodies.size(), 1u);
234
+ EXPECT_FALSE(inverted.bodies[0].isDisplayOnly());
235
+
236
+ // Two tetrahedra meeting at a single (non-manifold) vertex.
237
+ Evaluated pinched = evalSrc(
238
+ "polyhedron(points=[[0,0,0],[10,0,0],[0,10,0],[0,0,10],"
239
+ " [0,0,0],[-10,0,0],[0,-10,0],[0,0,-10]],"
240
+ " faces=[[0,2,1],[0,1,3],[1,2,3],[0,3,2],"
241
+ " [4,6,5],[4,5,7],[5,6,7],[4,7,6]]);");
242
+ ASSERT_EQ(pinched.bodies.size(), 1u);
243
+ EXPECT_FALSE(pinched.bodies[0].isDisplayOnly());
244
+ }
245
+
246
+ // A NaN coordinate is NOT drawable: it would poison the scene bounding box
247
+ // and send a renderer's camera auto-fit to infinity. Such a mesh keeps the
248
+ // old drop-it behaviour -- but now says so instead of vanishing mutely.
249
+ TEST(Polyhedron, NonFiniteVertexIsDiscardedNotDisplayed) {
250
+ std::string warning;
251
+ Evaluated e = evalSrc("polyhedron(points=[[0,0,0],[2,0,0],[0,2,0],[0/0,0,2]],"
252
+ " faces=[[0,1,2],[0,3,1],[0,2,3],[1,3,2]]);",
253
+ [&](const std::string& m) { warning = m; });
254
+ for (const ColoredBody& b : e.bodies) EXPECT_FALSE(b.isDisplayOnly());
255
+ EXPECT_NE(warning.find("NonFiniteVertex"), std::string::npos) << warning;
256
+ }
257
+
258
+ // It has no Manifold, so a boolean would have nothing to operate on. It is
259
+ // pulled aside like a `%` body and re-joined afterwards, leaving its valid
260
+ // siblings to merge normally.
261
+ TEST(Polyhedron, OpenMeshIsExcludedFromCsgButSurvivesIt) {
262
+ Evaluated e = evalSrc(std::string("union() { translate([50,0,0]) cube(4); ") + kOpenCube + " }");
263
+ ASSERT_EQ(e.bodies.size(), 2u);
264
+
265
+ int solids = 0, displayOnly = 0;
266
+ for (const ColoredBody& b : e.bodies) {
267
+ if (b.isDisplayOnly()) ++displayOnly;
268
+ else if (b.body && !b.body->IsEmpty()) {
269
+ ++solids;
270
+ EXPECT_NEAR(b.body->Volume(), 64.0, 1e-6); // the cube, unharmed
271
+ }
272
+ }
273
+ EXPECT_EQ(solids, 1);
274
+ EXPECT_EQ(displayOnly, 1);
275
+ }