openscad-cpp-evaluator 1.48.0__tar.gz → 1.49.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/bytecode.hpp +6 -1
  3. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/evaluator.hpp +5 -0
  4. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/pyproject.toml +1 -1
  5. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/booleans.cpp +14 -5
  6. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/builtins.hpp +2 -0
  7. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/control.cpp +1 -1
  8. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/topology.cpp +71 -86
  9. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/bytecode_compiler.cpp +9 -2
  10. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/bytecode_vm.cpp +5 -1
  11. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/stmt_eval.cpp +3 -1
  12. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_booleans.cpp +73 -10
  13. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/uv.lock +1 -1
  14. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/.github/workflows/ci.yml +0 -0
  15. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/.github/workflows/wheels.yml +0 -0
  16. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/.gitignore +0 -0
  17. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/.gitmodules +0 -0
  18. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/CLAUDE.md +0 -0
  19. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/LICENSE +0 -0
  21. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/README.md +0 -0
  22. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/bindings/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/bindings/ast_to_py.cpp +0 -0
  24. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/bindings/module.cpp +0 -0
  25. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/cmake/embed_font.cmake +0 -0
  26. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/cmake/patch_manifold.cmake +0 -0
  27. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/examples/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/examples/eval_perf_benchmark.cpp +0 -0
  29. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/examples/manifold_cache_reuse.cpp +0 -0
  30. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/examples/minimal_debugger.cpp +0 -0
  31. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  32. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/.gitignore +0 -0
  33. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  34. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  35. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/LICENSE +0 -0
  36. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/README.md +0 -0
  37. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  38. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  39. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  41. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  42. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  43. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  44. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  45. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  46. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  47. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  48. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  49. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  50. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  51. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  52. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  53. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  54. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  55. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/api.cpp +0 -0
  56. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  57. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  58. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  59. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  60. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  61. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  62. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  63. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  64. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  65. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  66. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  67. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  68. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  69. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  70. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  71. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  72. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/position.cpp +0 -0
  73. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  74. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  75. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  76. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  77. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  78. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  79. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  80. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  81. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  82. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  83. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  84. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  85. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  86. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  87. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  88. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  89. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  90. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  91. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  92. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  93. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_profile_time.cpp +0 -0
  94. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  95. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  96. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  97. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  98. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  99. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  100. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  101. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  102. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  103. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  104. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  105. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  106. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  107. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  108. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  109. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  110. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  111. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  112. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  113. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  114. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  115. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  116. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  117. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  118. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  119. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/export.hpp +0 -0
  120. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  121. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  122. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  123. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  124. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  125. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  126. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  127. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  128. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  129. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  130. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  131. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  132. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  133. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  134. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  135. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  136. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/value.hpp +0 -0
  137. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  138. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/python/openscad_cpp_evaluator/__init__.py +0 -0
  139. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/resources/fonts/LICENSE +0 -0
  140. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  141. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  142. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  143. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  144. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/CMakeLists.txt +0 -0
  145. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/call_args.cpp +0 -0
  146. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/color.cpp +0 -0
  147. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/dxf_dim.cpp +0 -0
  148. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/extrude.cpp +0 -0
  149. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/function_builtins.cpp +0 -0
  150. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/import.cpp +0 -0
  151. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/modifiers.cpp +0 -0
  152. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/primitives_2d.cpp +0 -0
  153. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/primitives_3d.cpp +0 -0
  154. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/profile_time.cpp +0 -0
  155. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/registry.cpp +0 -0
  156. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/roof.cpp +0 -0
  157. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/segments.cpp +0 -0
  158. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/surface.cpp +0 -0
  159. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/text.cpp +0 -0
  160. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/builtins/transforms.cpp +0 -0
  161. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/colored_body.cpp +0 -0
  162. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/coverage.cpp +0 -0
  163. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/csg_generate.cpp +0 -0
  164. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/csg_resolve.cpp +0 -0
  165. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/css_colors.cpp +0 -0
  166. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/debug/debug_repl.cpp +0 -0
  167. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/debug_profile.cpp +0 -0
  168. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/eval_context.cpp +0 -0
  169. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/eval_error.cpp +0 -0
  170. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/eval_use.cpp +0 -0
  171. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/export.cpp +0 -0
  172. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/expr_eval.cpp +0 -0
  173. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/format_closure.cpp +0 -0
  174. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/import/dxf_import.cpp +0 -0
  175. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/import/mesh_import.cpp +0 -0
  176. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/import/scene_import.cpp +0 -0
  177. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/import/surface_load.cpp +0 -0
  178. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/import/svg_import.cpp +0 -0
  179. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/manifold_cache.cpp +0 -0
  180. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/measure_geometry.cpp +0 -0
  181. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/mesh_check.cpp +0 -0
  182. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/text/font_match.cpp +0 -0
  183. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/text/freetype_font_provider.cpp +0 -0
  184. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/text/text_metrics.cpp +0 -0
  185. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/user_calls.cpp +0 -0
  186. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/utf8.cpp +0 -0
  187. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/value.cpp +0 -0
  188. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/xkcd_colors.cpp +0 -0
  189. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/xkcd_colors.hpp +0 -0
  190. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/src/zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_bytecode_compiler.cpp +0 -0
  193. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_children_separate.cpp +0 -0
  194. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_cli.cpp +0 -0
  195. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_control_flow.cpp +0 -0
  196. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_coverage.cpp +0 -0
  197. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_csg_tree.cpp +0 -0
  198. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_debug_hooks.cpp +0 -0
  199. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_discretizer.cpp +0 -0
  200. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_dxf_svg_import.cpp +0 -0
  201. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_eval_context.cpp +0 -0
  202. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_export.cpp +0 -0
  203. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_expr_eval.cpp +0 -0
  204. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_extrude_roof.cpp +0 -0
  205. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_feature_detection.cpp +0 -0
  206. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_function_builtins.cpp +0 -0
  207. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_helpers.hpp +0 -0
  208. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_import_export.cpp +0 -0
  209. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_include_cache.cpp +0 -0
  210. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_manifold_cache.cpp +0 -0
  211. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_mesh_check.cpp +0 -0
  212. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_multi_color_merge.cpp +0 -0
  213. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_openscad_parity.cpp +0 -0
  214. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_primitives.cpp +0 -0
  215. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_profile_time.cpp +0 -0
  216. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_profiling.cpp +0 -0
  217. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_python_bindings.py +0 -0
  218. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_render_expr.cpp +0 -0
  219. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_scope_trail.cpp +0 -0
  220. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_scoping.cpp +0 -0
  221. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_surface.cpp +0 -0
  222. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_tail_calls.cpp +0 -0
  223. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_text.cpp +0 -0
  224. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_transforms.cpp +0 -0
  225. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_undef_args.cpp +0 -0
  226. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_unexpected_args.cpp +0 -0
  227. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_use.cpp +0 -0
  228. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_value.cpp +0 -0
  229. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_viewport_params.cpp +0 -0
  230. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tests/test_zip_stored.cpp +0 -0
  231. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tools/cli/CMakeLists.txt +0 -0
  232. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tools/cli/cli_lib.cpp +0 -0
  233. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tools/cli/cli_lib.hpp +0 -0
  234. {openscad_cpp_evaluator-1.48.0 → openscad_cpp_evaluator-1.49.1}/tools/cli/main.cpp +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.48.0
3
+ Version: 1.49.1
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT AND OFL-1.1
@@ -406,9 +406,14 @@ enum class Op {
406
406
  // for an attachable()-style multi-body operand) and appends it, as a
407
407
  // Value, onto csgWrapStack.back().groupSizes -- exactly one entry per
408
408
  // top-level geometry statement, in source order, mirroring resolveCsg's
409
- // own `groupSizes.push_back(Value{...})` loop.
409
+ // own `groupSizes.push_back(Value{...})` loop. Bit 4 of `a`: the statement is an `if` (no else), an operand
410
+ // exactly when it took its branch, read from ev.lastIfTaken.
410
411
  CsgGroupEnd,
411
412
 
413
+ // Sets ev.lastIfTaken = (a != 0) as an `if` (no else) finishes. See
414
+ // Evaluator::lastIfTaken.
415
+ SetLastIfTaken,
416
+
412
417
  // One `children(..., separate=true)` statement inside a CSG bracket.
413
418
  // `a` interns the ModularCall in nativeStatements.
414
419
  //
@@ -228,6 +228,11 @@ public:
228
228
  // contributed by comparing this before/after evaluating just that one
229
229
  // statement.
230
230
  size_t currentTreeFrameSize() const { return treeStack_.back().size(); }
231
+ // Whether the last `if` (no else) to finish took its branch. Written as
232
+ // the statement ENDS, so after evaluating an outer `if` it describes
233
+ // that one and not an `if` nested inside it. Read by the CSG operand
234
+ // rule (emptyStatementIsAnOperand), which is its only reason to exist.
235
+ bool lastIfTaken = false;
231
236
 
232
237
 
233
238
  // Generate pass: walks `tree` bottom-up (children before their own
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.48.0"
7
+ version = "1.49.1"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -97,7 +97,12 @@ bool emptyStatementIsAnOperand(const oscad::ASTNode& stmt) {
97
97
  const oscad::NodeKind k = stmt.kind();
98
98
  return k == oscad::NodeKind::ModularCall || k == oscad::NodeKind::ModularFor ||
99
99
  k == oscad::NodeKind::ModularIntersectionFor || k == oscad::NodeKind::ModularLet ||
100
- k == oscad::NodeKind::ModularModifierShowOnly || k == oscad::NodeKind::ModularModifierHighlight;
100
+ k == oscad::NodeKind::ModularModifierShowOnly || k == oscad::NodeKind::ModularModifierHighlight ||
101
+ k == oscad::NodeKind::ModularIfElse;
102
+ }
103
+
104
+ bool emptyStatementIsAnOperand(const oscad::ASTNode& stmt, bool ifTaken) {
105
+ return emptyStatementIsAnOperand(stmt) || (stmt.kind() == oscad::NodeKind::ModularIf && ifTaken);
101
106
  }
102
107
 
103
108
  CSGParams resolveCsg(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
@@ -140,16 +145,20 @@ CSGParams resolveCsg(Evaluator& ev, const oscad::ModularCall& node, EvalContext&
140
145
  //
141
146
  // The reference builds a node for a module instantiation and for a
142
147
  // loop whatever they contain, so an empty one is an empty operand
143
- // and annihilates an intersection. An `if` that takes no branch, a
144
- // bare block, and a `*`-disabled statement build no node at all, so
145
- // they are not operands and must be skipped.
148
+ // and annihilates an intersection. So does an `if` that TAKES a
149
+ // branch, even one that draws nothing -- `if (true) {}` empties an
150
+ // intersection, and so does BOSL2's partition() when its child
151
+ // draws only one half (`if ($children>0) children();`). An `if`
152
+ // that takes no branch, a bare block, and a `*`-disabled statement
153
+ // build no node at all, so they are not operands and must be
154
+ // skipped. An if/else always takes one.
146
155
  //
147
156
  // Every line of this was checked against OpenSCAD rather than
148
157
  // reasoned about -- `intersection(){ cube(10); X; }` keeps the cube
149
158
  // for `if(false) sphere(6)`, `{ }` and `*cube(1)`, and comes out
150
159
  // empty for `for(i=[1:0]) sphere(6)`, `union(){}`, `group(){}` and
151
160
  // a call to an empty user module.
152
- emptyIsAGroup.push_back(Value{emptyStatementIsAnOperand(*geoNode)});
161
+ emptyIsAGroup.push_back(Value{emptyStatementIsAnOperand(*geoNode, ev.lastIfTaken)});
153
162
  backgroundStmt.push_back(Value{geoNode->kind() == oscad::NodeKind::ModularModifierBackground});
154
163
  }
155
164
 
@@ -264,6 +264,8 @@ std::vector<ColoredBody> generateSurface(Evaluator& ev, const CSGParams& params,
264
264
  // Whether a statement that produced no geometry still counts as a boolean
265
265
  // operand (and so empties an intersection) -- see resolveCsg.
266
266
  bool emptyStatementIsAnOperand(const oscad::ASTNode& stmt);
267
+ // The same, for a statement just evaluated: `ifTaken` is ev.lastIfTaken.
268
+ bool emptyStatementIsAnOperand(const oscad::ASTNode& stmt, bool ifTaken);
267
269
 
268
270
  // OpenSCAD's sin_degrees/cos_degrees/tan_degrees/atan2_degrees: exact at
269
271
  // multiples of 30 and 45 degrees.
@@ -138,7 +138,7 @@ CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersection
138
138
  const size_t before = ev.currentTreeFrameSize();
139
139
  ev.evalChildren(std::vector<const oscad::ASTNode*>{stmt}, parentCtx);
140
140
  groupSizes.push_back(Value{static_cast<double>(ev.currentTreeFrameSize() - before)});
141
- emptyIsAGroup.push_back(Value{emptyStatementIsAnOperand(*stmt)});
141
+ emptyIsAGroup.push_back(Value{emptyStatementIsAnOperand(*stmt, ev.lastIfTaken)});
142
142
  backgroundStmt.push_back(Value{stmt->kind() == oscad::NodeKind::ModularModifierBackground});
143
143
  }
144
144
  return;
@@ -651,27 +651,22 @@ std::vector<ColoredBody> generateMeshRepair(Evaluator& ev, const CSGParams& para
651
651
 
652
652
  // -- levelset(field, bounds, isovalue) -------------------------------------
653
653
  //
654
- // A solid from a grid of sampled values. Wraps Manifold::LevelSet with a C++
655
- // trilinear-sampling lambda over the array the script handed us.
654
+ // A solid from a grid of sampled values: the field array the script handed
655
+ // us, or a function sampled onto a grid at edge= spacing. The grid is then
656
+ // marched directly, as BOSL2's isosurface() does -- see marchingCubes() and
657
+ // the end of generateLevelSet for why Manifold::LevelSet is no longer used.
656
658
  //
657
- // Why a grid rather than Manifold's own SDF-callback shape, which would be
658
- // the obvious mapping: measured, building the field in OpenSCAD costs
659
- // 0.41 us/sample against 0.96 us for a closure call per sample, because the
660
- // arithmetic inlines into the list comprehension. More importantly a grid
661
- // means the C++ side never re-enters the evaluator, so canParallel can be
662
- // TRUE -- Manifold's docs warn that parallel policies "will crash language
663
- // runtimes with runtime locks that expect to not be called back by
664
- // unregistered threads", which is exactly what a callback design forfeits.
659
+ // Why offer an array at all, when a function is the obvious mapping:
660
+ // measured, building the field in OpenSCAD costs 0.41 us/sample against
661
+ // 0.96 us for a closure call per sample, because the arithmetic inlines into
662
+ // the list comprehension.
665
663
  //
666
- // The trade is memory: ~67 bytes per cell, so 200^3 is about 550 MB and that
667
- // is the practical ceiling. A callback samples lazily and has no ceiling.
664
+ // The trade is memory: ~67 bytes per cell for the array, so 200^3 is about
665
+ // 550 MB and that is the practical ceiling.
668
666
  //
669
- // Accuracy is bounded by the GRID, not by Manifold. At the grid's own
670
- // spacing (the default) the grid is marched directly, as BOSL2's
671
- // isosurface() does -- see marchingCubes(). An edge= asking for a different
672
- // spacing resamples it through LevelSet instead, which samples a
673
- // body-centred cubic lattice (sdf.cpp:440-446) that a plain cubic grid does
674
- // not line up with, so the lambda interpolates trilinearly between samples.
667
+ // Accuracy is bounded by the GRID. An array at its own spacing (the default)
668
+ // is marched as is; an edge= asking for a different spacing re-samples it
669
+ // trilinearly onto a grid at that spacing first.
675
670
 
676
671
  namespace {
677
672
 
@@ -1013,6 +1008,13 @@ manifold::MeshGL64 marchingCubes(const std::vector<double>& d, const size_t n[3]
1013
1008
  const int axis = (b - a) == 1 ? 0 : (b - a) == 2 ? 1 : 2;
1014
1009
  int64_t& id = edgeVert[3 * p[a] + static_cast<size_t>(axis)];
1015
1010
  if (id < 0) {
1011
+ // Linear, as BOSL2 interpolates. Root-finding each
1012
+ // vertex onto the true surface of a function field was
1013
+ // tried and measured (#745 on BelfrySCAD): 16% less mean
1014
+ // normal error on a sphere, but where the grid is too
1015
+ // coarse for the field it put real ripples on vertices
1016
+ // the grid cannot connect -- jagged, sliver-strewn --
1017
+ // where interpolating smooths them away, as BOSL2 does.
1016
1018
  const double t = v[a] / (v[a] - v[b]);
1017
1019
  const double x = org[0] + h[0] * (static_cast<double>(i + (a & 1)) + (axis == 0 ? t : 0.0));
1018
1020
  const double y = org[1] + h[1] * (static_cast<double>(j + (a >> 1 & 1)) + (axis == 1 ? t : 0.0));
@@ -1122,14 +1124,10 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1122
1124
  const oscad::ASTNode& node) {
1123
1125
  // Two shapes of field, and the choice matters beyond taste:
1124
1126
  //
1125
- // a GRID -- faster to sample (0.41 vs 0.96 us) and meshes in
1126
- // PARALLEL, because nothing re-enters the evaluator.
1127
- // Accuracy is capped by the grid; memory is the ceiling.
1128
- // a FUNCTION -- Manifold picks its own sample points and can snap to
1129
- // the true surface, so it is MORE accurate, with no
1130
- // memory ceiling. But every sample is a closure call, and
1131
- // canParallel must be false or the evaluator gets called
1132
- // back from threads it knows nothing about.
1127
+ // a GRID -- faster to sample (0.41 vs 0.96 us). Accuracy is capped
1128
+ // by the grid; memory is the ceiling.
1129
+ // a FUNCTION -- sampled onto a grid at edge= spacing, one closure call
1130
+ // per sample. Needs edge=, since nothing else says how fine.
1133
1131
  const Value& fieldArg = params.at("field");
1134
1132
  const ClosurePtr* fieldFn = std::get_if<ClosurePtr>(&fieldArg);
1135
1133
  // The array is NOT parsed yet: whether it should be field[i][j] or
@@ -1415,13 +1413,8 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1415
1413
  if (i0[a] > n[a] - 2) i0[a] = n[a] - 2;
1416
1414
  t[a] = g[a] - static_cast<double>(i0[a]);
1417
1415
  }
1418
- // Outside the sampled block the clamping above extends the boundary
1419
- // values outward, so a surface meeting the box runs straight on into
1420
- // the padding and the clip to `bounds` below leaves a clean plane.
1421
- // Answering "outside" there instead (as this once did) made Manifold
1422
- // close the surface right AT the box face, along its own lattice: a
1423
- // waffle-textured staircase INSIDE the bounds, which the clip could
1424
- // not remove (#745 on BelfrySCAD).
1416
+ // Clamped, though every grid node lies inside the block: the
1417
+ // floating-point position of the last node can land a hair past it.
1425
1418
 
1426
1419
  double acc = 0.0;
1427
1420
  for (int c = 0; c < 8; ++c) {
@@ -1433,62 +1426,54 @@ std::vector<ColoredBody> generateLevelSet(Evaluator& ev, const CSGParams& params
1433
1426
  return bandDistance(acc, isoLo, isoHi, invert);
1434
1427
  };
1435
1428
 
1436
- // Manifold closes the mesh where the surface meets the edge of the box it
1437
- // is given, and that closure follows the sample lattice -- a visible
1438
- // staircase on anything that reaches the boundary, which a gyroid does
1439
- // everywhere. So sample a PADDED box, let the ragged closure happen out
1440
- // there, and cut back to the box actually asked for. The cut is then a
1441
- // clean plane.
1429
+ // Every 3D field is marched on a REGULAR grid whose outermost nodes lie
1430
+ // exactly on `bounds`, as BOSL2's isosurface() marches: an array at its
1431
+ // own spacing as is, anything else sampled onto one first (a function by
1432
+ // calling it, an array re-sampled trilinearly).
1442
1433
  //
1443
- // Exactly the fix already applied to the 2D path (clipToBounds); it was
1444
- // not applied here at the time because the 2D error was the one measured.
1445
- const manifold::vec3 E = edgeVec ? manifold::vec3(edgeV[0], edgeV[1], edgeV[2]) : manifold::vec3(edge);
1446
- const manifold::vec3 pad = 2.0 * E;
1447
- manifold::Box bounds(manifold::vec3(origin[0], origin[1], origin[2]) - pad,
1448
- manifold::vec3((*hi)[0], (*hi)[1], (*hi)[2]) + pad);
1449
- // tolerance: Manifold root-finds along each crossing lattice edge until
1450
- // the bracket is within it (a distance along the edge, so independent of
1451
- // the field's units), instead of interpolating linearly between the two
1452
- // lattice samples. 1% of the edge. Measured on #745 on BelfrySCAD: the
1453
- // function form's vertices go from up to 0.2 off the true surface to on
1454
- // it, and the mean face-normal error on a per-axis edge halves to
1455
- // BOSL2's own; a grid gains too, since the trilinear field between
1456
- // samples is not linear along a lattice edge. Costs 11-20% more work.
1434
+ // This replaced Manifold::LevelSet for the function form and for an
1435
+ // array at another spacing (#745 on BelfrySCAD). LevelSet samples its own
1436
+ // body-centred lattice, whose irregular triangles shade as a field of
1437
+ // dimples even where every vertex is on the surface -- worse with a
1438
+ // per-axis edge, which meshed a squashed field and stretched it back. And
1439
+ // that lattice does not land on the box, so where the clip below cut it
1440
+ // the cut face did not follow the same grid as the rest of the surface.
1457
1441
  //
1458
- // canParallel is the crux: safe for a grid because the lambda is pure
1459
- // C++, and NOT safe for a function, which re-enters the evaluator.
1460
- // Manifold: parallel policies "will crash language runtimes with runtime
1461
- // locks that expect to not be called back by unregistered threads".
1462
- //
1463
- // LevelSet takes a single edge length, so a per-axis edge meshes a field
1464
- // squashed to unit spacing on every axis and stretches the result back.
1465
- // The tolerance there is 1% of the unit edge, so 1% of each axis's own.
1466
- const double tol = 0.01 * (edgeVec ? 1.0 : edge);
1467
- manifold::Manifold solid;
1468
- if (marchGrid) {
1469
- const size_t n[3] = {field->nx + 2, field->ny + 2, field->nz + 2};
1470
- std::vector<double> d(n[0] * n[1] * n[2], -1.0); // the "outside" ring
1471
- for (size_t k = 0; k < field->nz; ++k)
1472
- for (size_t j = 0; j < field->ny; ++j)
1473
- for (size_t i = 0; i < field->nx; ++i)
1474
- d[((k + 1) * n[1] + (j + 1)) * n[0] + (i + 1)] =
1475
- bandDistance(field->at(i, j, k), isoLo, isoHi, invert);
1476
- const std::array<double, 3> org{origin[0] - spacing[0], origin[1] - spacing[1], origin[2] - spacing[2]};
1477
- solid = manifold::Manifold(marchingCubes(d, n, org, spacing));
1478
- // Never expected; but a mesh Manifold refuses must not cost the
1479
- // user their shape, so fall back to resampling it.
1480
- if (solid.Status() != manifold::Manifold::Error::NoError) marchGrid = false;
1442
+ // A per-axis edge that does not divide the box is shrunk to the nearest
1443
+ // one that does, never grown: never coarser than the caller asked.
1444
+ std::array<double, 3> h = spacing;
1445
+ size_t cells[3];
1446
+ for (int a = 0; a < 3; ++a) {
1447
+ const double w = (*hi)[a] - origin[a];
1448
+ if (marchGrid) {
1449
+ cells[a] = static_cast<size_t>(std::llround(w / spacing[a]));
1450
+ } else {
1451
+ const double e = edgeVec ? edgeV[a] : edge;
1452
+ cells[a] = std::max<size_t>(1, static_cast<size_t>(std::ceil(w / e - 1e-9)));
1453
+ h[a] = w / static_cast<double>(cells[a]);
1454
+ }
1481
1455
  }
1482
- if (!marchGrid && !edgeVec) {
1483
- solid = fieldFn ? manifold::Manifold::LevelSet(sampleFn, bounds, edge, 0.0, tol, /*canParallel=*/false)
1484
- : manifold::Manifold::LevelSet(sampleGrid, bounds, edge, 0.0, tol, /*canParallel=*/true);
1485
- } else if (!marchGrid) {
1486
- const manifold::Box unit(bounds.min / E, bounds.max / E);
1487
- solid = fieldFn ? manifold::Manifold::LevelSet([&](manifold::vec3 q) { return sampleFn(q * E); },
1488
- unit, 1.0, 0.0, tol, /*canParallel=*/false)
1489
- : manifold::Manifold::LevelSet([&](manifold::vec3 q) { return sampleGrid(q * E); },
1490
- unit, 1.0, 0.0, tol, /*canParallel=*/true);
1491
- solid = solid.Scale(E);
1456
+ // One "outside" node beyond the box on every side, so every surface
1457
+ // closes; the clip below cuts that overhang back to the box.
1458
+ const size_t n[3] = {cells[0] + 3, cells[1] + 3, cells[2] + 3};
1459
+ std::vector<double> d(n[0] * n[1] * n[2], -1.0);
1460
+ for (size_t k = 0; k + 2 < n[2]; ++k)
1461
+ for (size_t j = 0; j + 2 < n[1]; ++j)
1462
+ for (size_t i = 0; i + 2 < n[0]; ++i) {
1463
+ const double x = origin[0] + h[0] * static_cast<double>(i);
1464
+ const double y = origin[1] + h[1] * static_cast<double>(j);
1465
+ const double z = origin[2] + h[2] * static_cast<double>(k);
1466
+ d[((k + 1) * n[1] + (j + 1)) * n[0] + (i + 1)] =
1467
+ marchGrid ? bandDistance(field->at(i, j, k), isoLo, isoHi, invert)
1468
+ : fieldFn ? sampleFn(manifold::vec3(x, y, z))
1469
+ : sampleGrid(manifold::vec3(x, y, z));
1470
+ }
1471
+ const std::array<double, 3> org{origin[0] - h[0], origin[1] - h[1], origin[2] - h[2]};
1472
+ manifold::Manifold solid(marchingCubes(d, n, org, h));
1473
+ if (solid.Status() != manifold::Manifold::Error::NoError) {
1474
+ // Never expected: marchingCubes closes by construction.
1475
+ ev.warn("levelset(): could not build a closed mesh from this field", &node.position());
1476
+ return {};
1492
1477
  }
1493
1478
 
1494
1479
  if (solid.IsEmpty()) return {};
@@ -1480,9 +1480,11 @@ public:
1480
1480
  compileStatementList(std::vector<const oscad::ASTNode*>{geoNode}, out);
1481
1481
  // `a` = whether a group that turns out EMPTY is still an
1482
1482
  // operand. See emptyStatementIsAnOperand (booleans.cpp).
1483
- // and bit 2 = it is a `%` statement, never an operand at all.
1483
+ // and bit 2 = it is a `%` statement, never an operand at all,
1484
+ // and bit 4 = an `if`, decided at run time.
1484
1485
  const int flags = (emptyStatementIsAnOperand(*geoNode) ? 1 : 0) |
1485
- (geoNode->kind() == oscad::NodeKind::ModularModifierBackground ? 2 : 0);
1486
+ (geoNode->kind() == oscad::NodeKind::ModularModifierBackground ? 2 : 0) |
1487
+ (geoNode->kind() == oscad::NodeKind::ModularIf ? 4 : 0);
1486
1488
  out.push_back({Op::CsgGroupEnd, flags, 0, nullptr});
1487
1489
  }
1488
1490
  }
@@ -1794,7 +1796,12 @@ public:
1794
1796
  out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1795
1797
  compileStatementList(n.trueBranch, out);
1796
1798
  out.push_back({Op::CloseExprScope, 0, 0, nullptr});
1799
+ out.push_back({Op::SetLastIfTaken, 1, 0, nullptr});
1800
+ const int jumpEndIdx = static_cast<int>(out.size());
1801
+ out.push_back({Op::Jump, 0, 0, nullptr});
1797
1802
  out[static_cast<size_t>(jumpFalseIdx)].b = static_cast<int>(out.size());
1803
+ out.push_back({Op::SetLastIfTaken, 0, 0, nullptr});
1804
+ out[static_cast<size_t>(jumpEndIdx)].a = static_cast<int>(out.size());
1798
1805
  return;
1799
1806
  }
1800
1807
  case NodeKind::ModularIfElse: {
@@ -1444,11 +1444,15 @@ Value driveVm(Evaluator& ev, size_t floor) {
1444
1444
  ++f.pc;
1445
1445
  break;
1446
1446
  }
1447
+ case Op::SetLastIfTaken:
1448
+ ev.lastIfTaken = ins.a != 0;
1449
+ ++f.pc;
1450
+ break;
1447
1451
  case Op::CsgGroupEnd: {
1448
1452
  PendingCsgWrap& pending = f.csgWrapStack.back();
1449
1453
  const size_t after = ev.treeStack_.back().size();
1450
1454
  pending.groupSizes.push_back(Value{static_cast<double>(after - pending.groupStartSize)});
1451
- pending.emptyIsAGroup.push_back(Value{(ins.a & 1) != 0});
1455
+ pending.emptyIsAGroup.push_back(Value{(ins.a & 1) != 0 || ((ins.a & 4) != 0 && ev.lastIfTaken)});
1452
1456
  pending.backgroundStmt.push_back(Value{(ins.a & 2) != 0});
1453
1457
  ++f.pc;
1454
1458
  break;
@@ -279,12 +279,14 @@ void Evaluator::evalStatement(const oscad::ASTNode& node, EvalContext& ctx) {
279
279
  // `_check_debug(branch[0] if branch else node, ctx, expr_level=True)`.
280
280
  case oscad::NodeKind::ModularIf: {
281
281
  auto& n = static_cast<const oscad::ModularIf&>(node);
282
- if (truthy(evalExprMaybeCompiled(*n.condition, ctx))) {
282
+ const bool taken = truthy(evalExprMaybeCompiled(*n.condition, ctx));
283
+ if (taken) {
283
284
  checkDebug(n.trueBranch.empty() ? node : *n.trueBranch.front(), ctx, /*forced=*/false,
284
285
  /*exprLevel=*/true);
285
286
  EvalContext branchCtx = blockScope(ctx);
286
287
  evalChildren(n.trueBranch, branchCtx);
287
288
  }
289
+ lastIfTaken = taken;
288
290
  return;
289
291
  }
290
292
  case oscad::NodeKind::ModularIfElse: {
@@ -11,6 +11,14 @@
11
11
  using namespace oscadeval;
12
12
  using namespace oscadeval::test;
13
13
 
14
+ namespace {
15
+ class ScopedVm {
16
+ public:
17
+ explicit ScopedVm(bool enabled) { Evaluator::setBytecodeVmEnabledForTesting(enabled); }
18
+ ~ScopedVm() { Evaluator::setBytecodeVmEnabledForTesting(std::nullopt); }
19
+ };
20
+ } // namespace
21
+
14
22
  // -- difference / intersection -------------------------------------------
15
23
 
16
24
  TEST(Difference, SubtractsInnerCubeVolume) {
@@ -66,10 +74,17 @@ TEST(Intersection, EmptyStatementsFollowTheReferenceExactly) {
66
74
  // falls out: a module instantiation or a loop builds a node whatever it
67
75
  // contains, so an empty one is an empty operand and annihilates; an
68
76
  // `if` with no branch taken, a bare block and a `*`-disabled statement
69
- // build no node at all and are not operands.
77
+ // build no node at all and are not operands. An `if` that takes a
78
+ // branch is one, whatever the branch draws.
70
79
  struct Case { const char* stmt; bool keepsTheCube; };
71
80
  const Case cases[] = {
72
81
  {"if (false) sphere(6);", true},
82
+ {"if (true) { }", false},
83
+ {"if (true) if (false) sphere(6);", false},
84
+ {"if (true) { x = 1; }", false},
85
+ {"if (false) sphere(6); else { }", false},
86
+ {"if (false) sphere(6); else if (false) sphere(6);", false},
87
+ {"{ if (false) sphere(6); }", true},
73
88
  {"{ }", true},
74
89
  {"*cube(1);", true},
75
90
  {"for (i = [1:0]) sphere(6);", false},
@@ -78,9 +93,16 @@ TEST(Intersection, EmptyStatementsFollowTheReferenceExactly) {
78
93
  {"let(x = 1) { }", false},
79
94
  {"#union() { }", false},
80
95
  };
96
+ // Each at top level and inside a module body, under both the
97
+ // interpreter and the VM, which decide an `if` separately.
98
+ for (const bool vm : {false, true})
99
+ for (const char* wrap : {"%s", "module w() { %s } w();"})
81
100
  for (const Case& c : cases) {
82
- const std::string src =
101
+ ScopedVm scoped(vm);
102
+ const std::string inner =
83
103
  std::string("intersection() { cube(10, center=true); ") + c.stmt + " }";
104
+ std::string src = wrap;
105
+ src.replace(src.find("%s"), 2, inner);
84
106
  Evaluated e = evalSrc(src);
85
107
  if (c.keepsTheCube) {
86
108
  ASSERT_EQ(e.bodies.size(), 1u) << src;
@@ -94,6 +116,24 @@ TEST(Intersection, EmptyStatementsFollowTheReferenceExactly) {
94
116
  }
95
117
  }
96
118
 
119
+ TEST(Intersection, BOSL2PartitionShowsOnlyTheHalfItsChildDraws) {
120
+ // partition()'s own shape: `intersection() { if ($children>0)
121
+ // children(); mask }`, once per half. A child drawing nothing for one
122
+ // half must empty that half, not leave its mask behind -- which is what
123
+ // `partition() if ($idx==0) cylinder(...)` drew. Verified against
124
+ // OpenSCAD: only the cube's own volume.
125
+ for (const bool vm : {false, true}) {
126
+ ScopedVm scoped(vm);
127
+ Evaluated e = evalSrc(
128
+ "module m() { intersection() { if ($children > 0) children(); cube(10); } }"
129
+ "m() if (false) sphere(5);");
130
+ const bool anyVolume = std::any_of(
131
+ e.bodies.begin(), e.bodies.end(),
132
+ [](const ColoredBody& b) { return b.body && !b.body->IsEmpty(); });
133
+ EXPECT_FALSE(anyVolume) << "vm=" << vm;
134
+ }
135
+ }
136
+
97
137
  TEST(Intersection, AnEmptyUserModuleIsStillAnOperand) {
98
138
  // The reference builds a node for a module instantiation whatever the
99
139
  // module's body does, so calling one that draws nothing empties the
@@ -1606,13 +1646,13 @@ TEST(LevelSet, AGridFieldIsAlsoCutCleanlyAtTheBox) {
1606
1646
  EXPECT_NEAR(capArea, 3.14159265358979 * r * r, 0.03 * 3.14159265358979 * r * r);
1607
1647
  }
1608
1648
 
1609
- TEST(LevelSetFn, VerticesLieOnTheTrueSurface) {
1610
- // A positive tolerance makes Manifold root-find along each lattice edge
1611
- // instead of interpolating linearly between its two samples, so a
1612
- // function field's vertices land on the surface itself. Measured on
1613
- // #745 on BelfrySCAD: 0.2 off at edge=2 before, on the surface after;
1614
- // the coarse, curved 1/r field there is where linear interpolation is
1615
- // worst.
1649
+ TEST(LevelSetFn, VerticesInterpolateAsBOSL2Does) {
1650
+ // #745 on BelfrySCAD, second round: a function field is sampled onto a
1651
+ // regular grid and marched, as BOSL2's isosurface() does, rather than
1652
+ // meshed by Manifold::LevelSet, whose body-centred lattice shaded as
1653
+ // dimples. Vertices are interpolated linearly along grid edges, so they
1654
+ // are near the surface, not on it: the 1/r field at edge=2 is where that
1655
+ // is worst. A per-axis edge must mesh as well as a plain one.
1616
1656
  for (const char* edge : {"2", "[1,2,0.4]"}) {
1617
1657
  Evaluated e = evalSrc(std::string("levelset(function(x,y,z) 5 / norm([x,y,z]), "
1618
1658
  "bounds=[[-8,-8,-8],[8,8,8]], isovalue=[1, 1/0], edge=") +
@@ -1623,7 +1663,8 @@ TEST(LevelSetFn, VerticesLieOnTheTrueSurface) {
1623
1663
  for (size_t v = 0; v + 2 < m.vertProperties.size(); v += m.numProp)
1624
1664
  worst = std::max(worst, std::abs(manifold::la::length(manifold::vec3(m.vertProperties[v], m.vertProperties[v + 1],
1625
1665
  m.vertProperties[v + 2])) - 5.0));
1626
- EXPECT_LT(worst, 0.03) << edge;
1666
+ EXPECT_LT(worst, 0.3) << edge;
1667
+ EXPECT_EQ(soleBody(e).Genus(), 0) << edge;
1627
1668
  }
1628
1669
  }
1629
1670
 
@@ -1701,6 +1742,28 @@ TEST(LevelSetGrid, AVectorEdgeWithoutBoundsSpacesEachAxis) {
1701
1742
  EXPECT_NEAR(b.min.z, -0.75, 1e-9); EXPECT_NEAR(b.max.z, 0.75, 1e-9);
1702
1743
  }
1703
1744
 
1745
+ TEST(LevelSetGrid, AFunctionIsMarchedOnAGridLandingOnTheBounds) {
1746
+ // Per-axis edge [1, 2, 0.4] over a 12 x 16 x 7 box: x and y divide it,
1747
+ // z does not (17.5 cells), so z is shrunk to 7/18 -- never coarsened --
1748
+ // and the grid's last nodes lie exactly on the box. Every vertex is then
1749
+ // on a grid edge.
1750
+ Evaluated e = evalSrc("levelset(function(x,y,z) 5 / norm([x,y,z]), isovalue=[1, 1/0], "
1751
+ "bounds=[[-6,-8,0],[6,8,7]], edge=[1,2,0.4]);");
1752
+ ASSERT_EQ(e.bodies.size(), 1u);
1753
+ EXPECT_GT(onGridLines(soleBody(e), manifold::vec3(-6, -8, 0), manifold::vec3(1, 2, 7.0 / 18.0)), 0.5);
1754
+ }
1755
+
1756
+ TEST(LevelSetGrid, AnArrayAtAnotherEdgeIsResampledOntoAGridLandingOnTheBounds) {
1757
+ // #745 on BelfrySCAD: an array resampled at an edge= not dividing the box
1758
+ // cut faces that did not follow the rest of the surface, since
1759
+ // LevelSet's lattice did not land on the box. 40/2.7 is 14.8 cells, so
1760
+ // the grid is 15 cells of 40/15 and the cut faces are its own.
1761
+ Evaluated e = evalSrc(sphereFieldSrc(41, 20) +
1762
+ "levelset(f, bounds=[[-20,-20,-20],[20,20,20]], isovalue=25, edge=2.7);");
1763
+ ASSERT_EQ(e.bodies.size(), 1u);
1764
+ EXPECT_GT(onGridLines(soleBody(e), manifold::vec3(-20.0), manifold::vec3(40.0 / 15.0)), 0.5);
1765
+ }
1766
+
1704
1767
  TEST(LevelSetGrid, A2DFieldArrayIsPlacedByEdgeToo) {
1705
1768
  // field[i][j] says 2D without a bounds to say so.
1706
1769
  Evaluated e = evalSrc("f = [for (i=[0:4]) [for (j=[0:2]) -1 ]];\nlevelset(f, edge=2);");
@@ -55,7 +55,7 @@ wheels = [
55
55
 
56
56
  [[package]]
57
57
  name = "openscad-cpp-evaluator"
58
- version = "1.48.0"
58
+ version = "1.49.1"
59
59
  source = { editable = "." }
60
60
  dependencies = [
61
61
  { name = "numpy" },