openscad-cpp-evaluator 0.13.10__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 (196) hide show
  1. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/bindings/module.cpp +9 -3
  3. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/colored_body.hpp +21 -0
  4. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/evaluator.hpp +12 -0
  5. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/booleans.cpp +15 -2
  7. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/builtins.hpp +13 -1
  8. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/extrude.cpp +12 -1
  9. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/function_builtins.cpp +42 -6
  10. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/import.cpp +33 -24
  11. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/primitives_3d.cpp +90 -22
  12. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/topology.cpp +2 -0
  13. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/transforms.cpp +44 -6
  14. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/csg_generate.cpp +23 -0
  15. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_extrude_roof.cpp +21 -5
  16. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_function_builtins.cpp +50 -0
  17. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_import_export.cpp +8 -1
  18. openscad_cpp_evaluator-0.15.0/tests/test_primitives.cpp +275 -0
  19. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_transforms.cpp +50 -0
  20. openscad_cpp_evaluator-0.15.0/uv.lock +65 -0
  21. openscad_cpp_evaluator-0.13.10/tests/test_primitives.cpp +0 -146
  22. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/.github/workflows/ci.yml +0 -0
  23. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/.github/workflows/wheels.yml +0 -0
  24. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/.gitignore +0 -0
  25. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/.gitmodules +0 -0
  26. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/CLAUDE.md +0 -0
  27. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/LICENSE +0 -0
  29. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/README.md +0 -0
  30. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/bindings/CMakeLists.txt +0 -0
  31. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/cmake/embed_font.cmake +0 -0
  32. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/examples/CMakeLists.txt +0 -0
  33. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/examples/eval_perf_benchmark.cpp +0 -0
  34. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/examples/manifold_cache_reuse.cpp +0 -0
  35. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/examples/minimal_debugger.cpp +0 -0
  36. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  37. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/.gitignore +0 -0
  38. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  39. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  40. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/README.md +0 -0
  41. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  42. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  43. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  44. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  45. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  46. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  47. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  48. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  49. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  50. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  51. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  52. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  53. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  54. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  55. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  56. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  57. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  58. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  59. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  60. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  61. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  62. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  63. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  64. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  65. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  66. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  67. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  68. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  69. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  70. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  71. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  72. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  73. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  74. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  75. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  76. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  77. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  78. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  79. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  80. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  81. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  82. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  83. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  84. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  85. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  86. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  87. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  88. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  89. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  90. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  91. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  92. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  93. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  94. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  95. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  96. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  97. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  98. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  99. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  100. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  101. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  102. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  103. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  104. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  105. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  106. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  107. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  108. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  109. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  110. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  111. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  112. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  113. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  114. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  115. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  116. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  117. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  118. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  119. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  120. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  121. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  122. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  123. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  124. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  125. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  126. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  127. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  128. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  129. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  130. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  131. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  132. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/resources/fonts/LICENSE +0 -0
  133. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  134. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/CMakeLists.txt +0 -0
  135. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/call_args.cpp +0 -0
  136. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/color.cpp +0 -0
  137. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/control.cpp +0 -0
  138. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/modifiers.cpp +0 -0
  139. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/primitives_2d.cpp +0 -0
  140. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/registry.cpp +0 -0
  141. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/roof.cpp +0 -0
  142. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/segments.cpp +0 -0
  143. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/surface.cpp +0 -0
  144. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/builtins/text.cpp +0 -0
  145. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/bytecode_compiler.cpp +0 -0
  146. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/bytecode_vm.cpp +0 -0
  147. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/colored_body.cpp +0 -0
  148. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/csg_resolve.cpp +0 -0
  149. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/css_colors.cpp +0 -0
  150. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/debug/debug_repl.cpp +0 -0
  151. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/debug_profile.cpp +0 -0
  152. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/eval_context.cpp +0 -0
  153. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/eval_error.cpp +0 -0
  154. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/eval_use.cpp +0 -0
  155. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/export.cpp +0 -0
  156. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/expr_eval.cpp +0 -0
  157. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/import/dxf_import.cpp +0 -0
  158. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/import/mesh_import.cpp +0 -0
  159. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/import/surface_load.cpp +0 -0
  160. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/import/svg_import.cpp +0 -0
  161. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/manifold_cache.cpp +0 -0
  162. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/stmt_eval.cpp +0 -0
  163. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/text/stb_font_provider.cpp +0 -0
  164. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/text/text_metrics.cpp +0 -0
  165. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/user_calls.cpp +0 -0
  166. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/value.cpp +0 -0
  167. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/src/zip_stored.cpp +0 -0
  168. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/CMakeLists.txt +0 -0
  169. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_booleans.cpp +0 -0
  170. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_bytecode_compiler.cpp +0 -0
  171. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_cli.cpp +0 -0
  172. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_control_flow.cpp +0 -0
  173. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_csg_tree.cpp +0 -0
  174. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_debug_hooks.cpp +0 -0
  175. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_dxf_svg_import.cpp +0 -0
  176. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_eval_context.cpp +0 -0
  177. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_export.cpp +0 -0
  178. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_expr_eval.cpp +0 -0
  179. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_helpers.hpp +0 -0
  180. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_manifold_cache.cpp +0 -0
  181. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_multi_color_merge.cpp +0 -0
  182. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_profiling.cpp +0 -0
  183. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_python_bindings.py +0 -0
  184. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_scope_trail.cpp +0 -0
  185. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_scoping.cpp +0 -0
  186. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_surface.cpp +0 -0
  187. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_tail_calls.cpp +0 -0
  188. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_text.cpp +0 -0
  189. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_use.cpp +0 -0
  190. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_value.cpp +0 -0
  191. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_viewport_params.cpp +0 -0
  192. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tests/test_zip_stored.cpp +0 -0
  193. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tools/cli/CMakeLists.txt +0 -0
  194. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tools/cli/cli_lib.cpp +0 -0
  195. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tools/cli/cli_lib.hpp +0 -0
  196. {openscad_cpp_evaluator-0.13.10 → openscad_cpp_evaluator-0.15.0}/tools/cli/main.cpp +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.13.10
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.13.10"
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
@@ -181,8 +181,19 @@ std::vector<ColoredBody> generateProjection(Evaluator&, const CSGParams& params,
181
181
  BuiltinWrapParams computeOffsetParams(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
182
182
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
183
183
 
184
- const Value rArg = getArg(args, std::nullopt, "r", Value{});
184
+ // `r` owns position 0 (offset(2) == offset(r=2)); delta/chamfer are
185
+ // named-only, matching the reference. `r` also wins outright when both
186
+ // are given -- the reference never even looks at delta in that case.
187
+ Value rArg = getArg(args, 0, "r", Value{});
185
188
  const Value deltaArg = getArg(args, std::nullopt, "delta", Value{});
189
+ // A bare offset() is offset(r=1), not a pass-through: the reference
190
+ // initialises delta to 1 with a round join and only ever overwrites it
191
+ // from an argument, so "neither given" lands on the same state "r=1"
192
+ // does. Verified against OpenSCAD 2022.08.22 -- offset() and offset(r=1)
193
+ // produce a byte-identical mesh for the same input.
194
+ if (std::holds_alternative<std::monostate>(rArg) && std::holds_alternative<std::monostate>(deltaArg)) {
195
+ rArg = Value{1.0};
196
+ }
186
197
  const bool chamfer = truthy(getArg(args, std::nullopt, "chamfer", Value{false}));
187
198
 
188
199
  CSGParams params;
@@ -279,7 +279,13 @@ Value builtinSearch(const CallArgs& args) {
279
279
  }
280
280
 
281
281
  Value builtinLookup(const CallArgs& args) {
282
- const double key = toDoubleLenient(getArg(args, 0, "key", Value{}));
282
+ // Guarded here rather than via scalarNumericArity() -- the second
283
+ // argument is a table, so the all-arguments-are-numbers rule can't
284
+ // apply. Without this, toDoubleLenient() turns a non-numeric key into
285
+ // 0.0 and the lookup silently returns the table's first value.
286
+ const Value keyArg = getArg(args, 0, "key", Value{});
287
+ if (!std::holds_alternative<double>(keyArg)) return Value{};
288
+ const double key = std::get<double>(keyArg);
283
289
  const Value tableArg = getArg(args, 1, "table", Value{});
284
290
  const ListPtr* tablePtr = std::get_if<ListPtr>(&tableArg);
285
291
  if (!tablePtr || !*tablePtr) return Value{};
@@ -359,11 +365,35 @@ Value builtinFontmetrics(Evaluator& ev, const CallArgs& args) {
359
365
  });
360
366
  }
361
367
 
362
- const std::unordered_set<std::string>& numericOnlyNames() {
363
- static const std::unordered_set<std::string> names = {
364
- "abs", "sign", "ceil", "floor", "round", "sqrt", "ln", "log", "exp", "sin", "cos", "tan",
365
- "asin", "acos", "atan", "atan2", "pow", "max", "min", "norm", "cross",
368
+ // Take only scalar numbers, mapped to how many leading positional
369
+ // arguments must each actually BE a number. Anything else there (undef, a
370
+ // string, a list, a bool, an object) is a type error yielding undef, and
371
+ // so is supplying fewer arguments than the arity -- matching the
372
+ // reference, which rejects the call outright rather than coercing.
373
+ //
374
+ // This has to be checked up front rather than left to each case below:
375
+ // toDoubleLenient() silently turns undef/string/list into 0.0, which
376
+ // would make `cos(misspelled_var)` quietly 1 instead of undef, and a
377
+ // typo'd variable then produces plausible-looking geometry instead of an
378
+ // obvious failure. Arity is deliberately a floor, not an exact match --
379
+ // the reference also rejects EXTRA arguments, but tightening that too
380
+ // risks breaking working library code for no correctness gain here.
381
+ const std::unordered_map<std::string, size_t>& scalarNumericArity() {
382
+ static const std::unordered_map<std::string, size_t> arity = {
383
+ {"abs", 1}, {"sign", 1}, {"ceil", 1}, {"floor", 1}, {"round", 1}, {"sqrt", 1},
384
+ {"ln", 1}, {"log", 1}, {"exp", 1}, {"sin", 1}, {"cos", 1}, {"tan", 1},
385
+ {"asin", 1}, {"acos", 1}, {"atan", 1}, {"atan2", 2}, {"pow", 2}, {"rands", 3},
366
386
  };
387
+ return arity;
388
+ }
389
+
390
+ // Numeric functions that legitimately take a LIST argument, so the
391
+ // all-arguments-must-be-numbers rule above can't apply -- each validates
392
+ // its own operands, and only needs the bool case caught here (Value's
393
+ // bool and double are distinct alternatives, but the reference rejects
394
+ // `max(true, 1)` as a type error rather than treating true as 1).
395
+ const std::unordered_set<std::string>& numericOnlyNames() {
396
+ static const std::unordered_set<std::string> names = {"max", "min", "norm", "cross"};
367
397
  return names;
368
398
  }
369
399
 
@@ -479,7 +509,13 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
479
509
  // elsewhere before reaching here) -- mirrors the old chain's fallthrough.
480
510
  if (idIt == ids.end()) return Value{};
481
511
 
482
- if (numericOnlyNames().count(name)) {
512
+ if (const auto arityIt = scalarNumericArity().find(name); arityIt != scalarNumericArity().end()) {
513
+ const std::vector<Value> positional = allPositional(args);
514
+ if (positional.size() < arityIt->second) return Value{};
515
+ for (size_t i = 0; i < arityIt->second; ++i) {
516
+ if (!std::holds_alternative<double>(positional[i])) return Value{};
517
+ }
518
+ } else if (numericOnlyNames().count(name)) {
483
519
  for (const Value& v : allPositional(args)) {
484
520
  if (isBoolOrListContainsBool(v)) return Value{};
485
521
  }
@@ -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.
@@ -144,34 +183,38 @@ std::vector<ColoredBody> generateSphere(Evaluator& ev, const CSGParams& params,
144
183
  CSGParams resolveCylinder(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
145
184
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
146
185
  const double h = toDoubleLenient(getArg(args, 0, "h", Value{1.0}));
147
- Value rV = getArg(args, 1, "r", Value{});
148
- Value r1V = getArg(args, std::nullopt, "r1", Value{});
149
- Value r2V = getArg(args, std::nullopt, "r2", Value{});
186
+ // Positional order is (h, r1, r2, center); r/d/d1/d2 are named-only.
187
+ // NOTE r1/r2 -- not r -- own positions 1 and 2, so `cylinder(10, 5, 2)`
188
+ // is a cone, and `cylinder(10, 5)` is a cone tapering to the r2 default
189
+ // of 1 rather than a straight r=5 cylinder. Both match the reference.
190
+ Value rV = getArg(args, std::nullopt, "r", Value{});
150
191
  Value dV = getArg(args, std::nullopt, "d", Value{});
192
+ Value r1V = getArg(args, 1, "r1", Value{});
193
+ Value r2V = getArg(args, 2, "r2", Value{});
151
194
  Value d1V = getArg(args, std::nullopt, "d1", Value{});
152
195
  Value d2V = getArg(args, std::nullopt, "d2", Value{});
153
- const bool center = truthy(getArg(args, std::nullopt, "center", Value{false}));
154
-
155
- std::optional<double> r = isUndef(rV) ? std::nullopt : std::optional<double>(toDoubleLenient(rV));
156
- std::optional<double> r1 = isUndef(r1V) ? std::nullopt : std::optional<double>(toDoubleLenient(r1V));
157
- std::optional<double> r2 = isUndef(r2V) ? std::nullopt : std::optional<double>(toDoubleLenient(r2V));
158
-
159
- if (!isUndef(dV) && !r) r = toDoubleLenient(dV) / 2.0;
160
- if (!isUndef(d1V) && !r1) r1 = toDoubleLenient(d1V) / 2.0;
161
- if (!isUndef(d2V) && !r2) r2 = toDoubleLenient(d2V) / 2.0;
162
- if (r) {
163
- r1 = *r;
164
- r2 = *r;
165
- }
166
- if (!r1) r1 = 1.0;
167
- if (!r2) r2 = *r1;
168
-
169
- const int segs = fnSegmentsFromCtx(effCtx, std::max(*r1, *r2));
196
+ const bool center = truthy(getArg(args, 3, "center", Value{false}));
197
+
198
+ // Application order is significant and mirrors the reference's own
199
+ // sequence of independent `if (x.isNumber()) ...` assignments: r, then
200
+ // d (which overrides r outright rather than deferring to it), then the
201
+ // per-end r1/r2, then the per-end d1/d2. So `cylinder(h=10, d=8, r1=5)`
202
+ // is r1=5/r2=4, and `cylinder(h=10, r=5, r2=2)` is r1=5/r2=2. Both ends
203
+ // default to 1 independently -- r2 does NOT fall back to r1.
204
+ double r1 = 1.0, r2 = 1.0;
205
+ if (!isUndef(rV)) r1 = r2 = toDoubleLenient(rV);
206
+ if (!isUndef(dV)) r1 = r2 = toDoubleLenient(dV) / 2.0;
207
+ if (!isUndef(r1V)) r1 = toDoubleLenient(r1V);
208
+ if (!isUndef(r2V)) r2 = toDoubleLenient(r2V);
209
+ if (!isUndef(d1V)) r1 = toDoubleLenient(d1V) / 2.0;
210
+ if (!isUndef(d2V)) r2 = toDoubleLenient(d2V) / 2.0;
211
+
212
+ const int segs = fnSegmentsFromCtx(effCtx, std::max(r1, r2));
170
213
 
171
214
  CSGParams params;
172
215
  params["h"] = Value{h};
173
- params["r1"] = Value{*r1};
174
- params["r2"] = Value{*r2};
216
+ params["r1"] = Value{r1};
217
+ params["r2"] = Value{r2};
175
218
  params["center"] = Value{center};
176
219
  params["segs"] = Value{static_cast<double>(segs)};
177
220
  params["color"] = colorToValue(effCtx.color);
@@ -288,6 +331,31 @@ std::vector<ColoredBody> generatePolyhedron(Evaluator& ev, const CSGParams& para
288
331
  mesh.triVerts.push_back(static_cast<uint32_t>(std::get<double>(t)));
289
332
  }
290
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
+ }
291
359
  return {ev.tagGenerated(std::move(body), node, params.at("color"))};
292
360
  }
293
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) {
@@ -3,6 +3,7 @@
3
3
  #include "openscad_cpp_evaluator/call_args.hpp"
4
4
  #include "openscad_cpp_evaluator/evaluator.hpp"
5
5
 
6
+ #include <array>
6
7
  #include <cmath>
7
8
  #include <numbers>
8
9
 
@@ -32,6 +33,21 @@ manifold::vec2 toVec2(const Value& v, double defaultX = 0.0, double defaultY = 0
32
33
  return manifold::vec2(defaultX, defaultY);
33
34
  }
34
35
 
36
+ // resize()'s `auto` argument: a bare bool applies to all three axes, a
37
+ // list applies element-wise (missing elements are false), anything else
38
+ // (including undef) disables auto entirely. Mirrors the reference's
39
+ // Value::toBool()/vector handling.
40
+ std::array<bool, 3> toAutoAxes(const Value& v) {
41
+ if (const bool* b = std::get_if<bool>(&v)) return {*b, *b, *b};
42
+ if (const ListPtr* l = std::get_if<ListPtr>(&v); l && *l) {
43
+ const auto& items = (*l)->items;
44
+ std::array<bool, 3> out{false, false, false};
45
+ for (size_t i = 0; i < 3 && i < items.size(); ++i) out[i] = truthy(items[i]);
46
+ return out;
47
+ }
48
+ return {false, false, false};
49
+ }
50
+
35
51
  // Rodrigues' rotation formula as a 3x4 transform (rotate(angle, axis) --
36
52
  // the axis form, as opposed to rotate([x,y,z]) Euler angles, which uses
37
53
  // Manifold's own Rotate() directly and never needs this). Mirrors
@@ -117,13 +133,35 @@ manifold::Manifold applyTransform3d(const std::string& name, const CallArgs& arg
117
133
  return body.Mirror(toVec3(getArg(args, 0, "v", Value{}), 1.0, 0.0, 0.0));
118
134
  }
119
135
  if (name == "resize") {
120
- const manifold::vec3 newSize = toVec3(getArg(args, 0, "newsize", Value{}));
136
+ const manifold::vec3 newSizeV = toVec3(getArg(args, 0, "newsize", Value{}));
137
+ std::array<bool, 3> autoAxes = toAutoAxes(getArg(args, 1, "auto", Value{}));
121
138
  const manifold::Box bbox = body.BoundingBox();
122
- const manifold::vec3 span = bbox.max - bbox.min;
123
- const double sx = (newSize.x != 0.0 && span.x != 0.0) ? newSize.x / span.x : 1.0;
124
- const double sy = (newSize.y != 0.0 && span.y != 0.0) ? newSize.y / span.y : 1.0;
125
- const double sz = (newSize.z != 0.0 && span.z != 0.0) ? newSize.z / span.z : 1.0;
126
- return body.Scale(manifold::vec3(sx, sy, sz));
139
+ const manifold::vec3 spanV = bbox.max - bbox.min;
140
+ const double ns[3] = {newSizeV.x, newSizeV.y, newSizeV.z};
141
+ const double span[3] = {spanV.x, spanV.y, spanV.z};
142
+
143
+ // An `auto` axis takes the scale factor of whichever axis asked for
144
+ // the LARGEST new size -- not its own (it has none) and not the
145
+ // first specified one. Mirrors the reference's newsizemax_index:
146
+ // ties keep the earlier axis, and an all-zero newsize leaves the
147
+ // factor at 1 so `resize([0,0,0], true)` is a no-op.
148
+ int maxIdx = 0;
149
+ for (int i = 1; i < 3; ++i) {
150
+ if (ns[i] > ns[maxIdx]) maxIdx = i;
151
+ }
152
+ const double autoScale = (ns[maxIdx] != 0.0 && span[maxIdx] != 0.0) ? ns[maxIdx] / span[maxIdx] : 1.0;
153
+
154
+ double scale[3];
155
+ for (int i = 0; i < 3; ++i) {
156
+ if (ns[i] != 0.0 && span[i] != 0.0) {
157
+ scale[i] = ns[i] / span[i];
158
+ } else if (ns[i] == 0.0 && autoAxes[static_cast<size_t>(i)]) {
159
+ scale[i] = autoScale;
160
+ } else {
161
+ scale[i] = 1.0;
162
+ }
163
+ }
164
+ return body.Scale(manifold::vec3(scale[0], scale[1], scale[2]));
127
165
  }
128
166
  if (name == "multmatrix") {
129
167
  Value m = getArg(args, 0, "m", Value{});
@@ -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
@@ -151,11 +151,27 @@ TEST(Offset, NegativeDeltaShrinksTheSquare) {
151
151
  EXPECT_NEAR(e.bodies[0].section->Area(), 2.0 * 2.0, 1e-6);
152
152
  }
153
153
 
154
- TEST(Offset, NoRadiusOrDeltaPassesThroughFirstChild) {
155
- Evaluated e = evalSrc("offset() square(4, center=true);");
156
- ASSERT_EQ(e.bodies.size(), 1u);
157
- ASSERT_TRUE(e.bodies[0].section.has_value());
158
- EXPECT_NEAR(e.bodies[0].section->Area(), 16.0, 1e-6);
154
+ // `r` owns position 0, so offset(2) == offset(r=2). It used to be
155
+ // named-only, making a bare offset(2) a silent no-op.
156
+ TEST(Offset, FirstPositionalArgumentIsRadius) {
157
+ Evaluated positional = evalSrc("offset(1, $fn=64) square(4, center=true);");
158
+ Evaluated named = evalSrc("offset(r=1, $fn=64) square(4, center=true);");
159
+ ASSERT_TRUE(positional.bodies[0].section.has_value());
160
+ EXPECT_NEAR(positional.bodies[0].section->Area(), named.bodies[0].section->Area(), 1e-9);
161
+ EXPECT_GT(positional.bodies[0].section->Area(), 16.0);
162
+ }
163
+
164
+ // A bare offset() is offset(r=1), NOT a pass-through -- the reference
165
+ // initialises its delta to 1 with a round join and only overwrites it from
166
+ // an argument. Verified against OpenSCAD 2022.08.22, where offset() and
167
+ // offset(r=1) produce identical geometry.
168
+ TEST(Offset, NoRadiusOrDeltaDefaultsToRadiusOne) {
169
+ Evaluated bare = evalSrc("offset() square(4, center=true);");
170
+ Evaluated explicitR = evalSrc("offset(r=1) square(4, center=true);");
171
+ ASSERT_EQ(bare.bodies.size(), 1u);
172
+ ASSERT_TRUE(bare.bodies[0].section.has_value());
173
+ EXPECT_NEAR(bare.bodies[0].section->Area(), explicitR.bodies[0].section->Area(), 1e-9);
174
+ EXPECT_GT(bare.bodies[0].section->Area(), 16.0); // grew, rather than passing through
159
175
  }
160
176
 
161
177
  // -- roof -----------------------------------------------------------------