openscad-cpp-evaluator 1.38.1__tar.gz → 1.40.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 (232) hide show
  1. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/pyproject.toml +1 -1
  3. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/booleans.cpp +2 -1
  4. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/builtins.hpp +8 -0
  5. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/function_builtins.cpp +7 -7
  6. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/primitives_3d.cpp +116 -14
  7. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/registry.cpp +3 -3
  8. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/text.cpp +16 -2
  9. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/colored_body.cpp +3 -2
  10. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/csg_generate.cpp +9 -2
  11. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_manifold_cache.cpp +23 -0
  12. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_primitives.cpp +40 -5
  13. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_text.cpp +30 -0
  14. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/uv.lock +1 -1
  15. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/.github/workflows/ci.yml +0 -0
  16. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/.github/workflows/wheels.yml +0 -0
  17. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/.gitignore +0 -0
  18. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/.gitmodules +0 -0
  19. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/CLAUDE.md +0 -0
  20. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/CMakeLists.txt +0 -0
  21. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/LICENSE +0 -0
  22. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/README.md +0 -0
  23. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/bindings/CMakeLists.txt +0 -0
  24. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/bindings/ast_to_py.cpp +0 -0
  25. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/bindings/module.cpp +0 -0
  26. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/cmake/embed_font.cmake +0 -0
  27. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/cmake/patch_manifold.cmake +0 -0
  28. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/examples/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/examples/eval_perf_benchmark.cpp +0 -0
  30. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/examples/manifold_cache_reuse.cpp +0 -0
  31. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/examples/minimal_debugger.cpp +0 -0
  32. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  33. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/.gitignore +0 -0
  34. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  35. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  36. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/README.md +0 -0
  37. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  38. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  39. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  41. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  42. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  43. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  44. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  45. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  46. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  47. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  48. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  49. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  50. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  51. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  52. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  53. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  54. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  55. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  56. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  57. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  58. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  59. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  60. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  61. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  62. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  63. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  64. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  65. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  66. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  67. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  68. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  69. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  70. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  71. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  72. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  73. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  74. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  75. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  76. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  77. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  78. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  79. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  80. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  81. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  82. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  83. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  84. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  85. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  86. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  87. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  88. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  89. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  90. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  91. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  92. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  93. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_profile_time.cpp +0 -0
  94. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  95. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  96. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  97. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  98. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  99. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  100. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  101. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  102. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  103. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  104. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  105. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  106. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  107. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  108. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  109. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  110. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  111. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  112. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  113. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  114. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  115. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  116. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  117. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  118. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  119. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  120. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  121. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  122. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  123. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  124. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  125. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  126. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  127. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  128. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  129. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  130. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  131. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  132. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  133. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  134. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  135. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  136. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  137. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  138. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  139. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  140. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  141. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/resources/fonts/LICENSE +0 -0
  142. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  143. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  144. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  145. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  146. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/CMakeLists.txt +0 -0
  147. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/call_args.cpp +0 -0
  148. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/color.cpp +0 -0
  149. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/control.cpp +0 -0
  150. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/dxf_dim.cpp +0 -0
  151. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/extrude.cpp +0 -0
  152. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/import.cpp +0 -0
  153. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/modifiers.cpp +0 -0
  154. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/primitives_2d.cpp +0 -0
  155. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/profile_time.cpp +0 -0
  156. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/roof.cpp +0 -0
  157. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/segments.cpp +0 -0
  158. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/surface.cpp +0 -0
  159. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/topology.cpp +0 -0
  160. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/builtins/transforms.cpp +0 -0
  161. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/bytecode_compiler.cpp +0 -0
  162. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/bytecode_vm.cpp +0 -0
  163. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/coverage.cpp +0 -0
  164. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/csg_resolve.cpp +0 -0
  165. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/css_colors.cpp +0 -0
  166. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/debug/debug_repl.cpp +0 -0
  167. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/debug_profile.cpp +0 -0
  168. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/eval_context.cpp +0 -0
  169. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/eval_error.cpp +0 -0
  170. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/eval_use.cpp +0 -0
  171. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/export.cpp +0 -0
  172. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/expr_eval.cpp +0 -0
  173. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/format_closure.cpp +0 -0
  174. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/import/dxf_import.cpp +0 -0
  175. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/import/mesh_import.cpp +0 -0
  176. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/import/scene_import.cpp +0 -0
  177. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/import/surface_load.cpp +0 -0
  178. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/import/svg_import.cpp +0 -0
  179. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/manifold_cache.cpp +0 -0
  180. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/measure_geometry.cpp +0 -0
  181. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/mesh_check.cpp +0 -0
  182. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/stmt_eval.cpp +0 -0
  183. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/text/font_match.cpp +0 -0
  184. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/text/freetype_font_provider.cpp +0 -0
  185. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/text/text_metrics.cpp +0 -0
  186. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/user_calls.cpp +0 -0
  187. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/utf8.cpp +0 -0
  188. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/value.cpp +0 -0
  189. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/xkcd_colors.cpp +0 -0
  190. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/xkcd_colors.hpp +0 -0
  191. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/src/zip_stored.cpp +0 -0
  192. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/CMakeLists.txt +0 -0
  193. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_booleans.cpp +0 -0
  194. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_bytecode_compiler.cpp +0 -0
  195. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_children_separate.cpp +0 -0
  196. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_cli.cpp +0 -0
  197. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_control_flow.cpp +0 -0
  198. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_coverage.cpp +0 -0
  199. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_csg_tree.cpp +0 -0
  200. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_debug_hooks.cpp +0 -0
  201. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_discretizer.cpp +0 -0
  202. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_dxf_svg_import.cpp +0 -0
  203. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_eval_context.cpp +0 -0
  204. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_export.cpp +0 -0
  205. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_expr_eval.cpp +0 -0
  206. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_extrude_roof.cpp +0 -0
  207. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_feature_detection.cpp +0 -0
  208. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_function_builtins.cpp +0 -0
  209. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_helpers.hpp +0 -0
  210. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_import_export.cpp +0 -0
  211. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_include_cache.cpp +0 -0
  212. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_mesh_check.cpp +0 -0
  213. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_multi_color_merge.cpp +0 -0
  214. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_profile_time.cpp +0 -0
  215. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_profiling.cpp +0 -0
  216. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_python_bindings.py +0 -0
  217. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_render_expr.cpp +0 -0
  218. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_scope_trail.cpp +0 -0
  219. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_scoping.cpp +0 -0
  220. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_surface.cpp +0 -0
  221. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_tail_calls.cpp +0 -0
  222. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_transforms.cpp +0 -0
  223. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_undef_args.cpp +0 -0
  224. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_unexpected_args.cpp +0 -0
  225. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_use.cpp +0 -0
  226. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_value.cpp +0 -0
  227. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_viewport_params.cpp +0 -0
  228. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tests/test_zip_stored.cpp +0 -0
  229. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tools/cli/CMakeLists.txt +0 -0
  230. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tools/cli/cli_lib.cpp +0 -0
  231. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.0}/tools/cli/cli_lib.hpp +0 -0
  232. {openscad_cpp_evaluator-1.38.1 → openscad_cpp_evaluator-1.40.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: 1.38.1
3
+ Version: 1.40.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.38.1"
7
+ version = "1.40.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -231,7 +231,8 @@ manifold::Manifold finishKeepMinuend(Evaluator& ev, std::vector<KeepPart>& parts
231
231
  std::optional<manifold::Manifold> out;
232
232
  for (KeepPart& part : parts) {
233
233
  if (!part.body.body) continue;
234
- manifold::MeshGL mesh = part.body.body->GetMeshGL();
234
+ // MeshGL64: rebuilt below, and float32 would move every vertex.
235
+ manifold::MeshGL64 mesh = part.body.body->GetMeshGL64();
235
236
  if (mesh.triVerts.empty()) continue;
236
237
  std::unordered_map<uint32_t, uint32_t> remap;
237
238
  for (uint32_t& id : mesh.runOriginalID) {
@@ -268,6 +268,14 @@ std::vector<ColoredBody> generateSurface(Evaluator& ev, const CSGParams& params,
268
268
 
269
269
  // text() -- text.cpp.
270
270
  CSGParams resolveText(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
271
+
272
+ // The font size text()/textmetrics()/fontmetrics() draw at: `size`
273
+ // (default 10), or `em` * 72/100 when `em` is given -- upstream's
274
+ // correction for the 100/72 factor that makes `size` the cap height
275
+ // rather than the em square (openscad #4304). `size` given alongside
276
+ // `em` is ignored, with upstream's warning naming `caller`.
277
+ double textSizeArg(Evaluator& ev, const CallArgs& args, std::optional<int> sizePos, std::optional<int> emPos,
278
+ const char* caller, const oscad::Position* where);
271
279
  std::vector<ColoredBody> generateText(Evaluator& ev, const CSGParams& params,
272
280
  const std::vector<std::unique_ptr<CSGNode>>& children, const oscad::ASTNode& node);
273
281
 
@@ -776,7 +776,7 @@ Value objectOf(std::vector<std::pair<std::string, Value>> items) {
776
776
  }
777
777
 
778
778
  // textmetrics(text=, size=10, font=, direction=, language=, script=,
779
- // halign=, valign=, spacing=) -- measures `text` against the
779
+ // halign=, valign=, spacing=, em=) -- measures `text` against the
780
780
  // FontProvider-resolved font, laid out by exactly the same shaping call
781
781
  // text() makes, and returns an OscObject with position/size/ascent/
782
782
  // descent/offset/advance in real OpenSCAD's key order. Going through the
@@ -784,10 +784,10 @@ Value objectOf(std::vector<std::pair<std::string, Value>> items) {
784
784
  // a script positions against cannot disagree with what it draws.
785
785
  Value builtinTextmetrics(Evaluator& ev, const CallArgs& args, const oscad::Position* pos) {
786
786
  const std::string text = asStringOr(getArg(args, 0, "text", Value{std::string("")}), "");
787
- const double size = toDoubleLenient(getArg(args, 1, "size", Value{10.0}));
787
+ const double size = textSizeArg(ev, args, 1, 9, "textmetrics", pos);
788
788
  // Positional indices, all of them: OpenSCAD's own signature is
789
789
  // textmetrics(text, size, font, direction, language, script, halign,
790
- // valign, spacing) and it honours every one of those positionally --
790
+ // valign, spacing, em) and it honours every one of those positionally --
791
791
  // verified against the 2026.02.01 binary, not read off a doc page.
792
792
  // `font` being name-only here is what made
793
793
  // `textmetrics("Hi", 10, "Liberation Sans:style=Bold")` silently
@@ -817,10 +817,10 @@ Value builtinTextmetrics(Evaluator& ev, const CallArgs& args, const oscad::Posit
817
817
  });
818
818
  }
819
819
 
820
- // fontmetrics(size=10, font=) -- global metrics of the FontProvider-
820
+ // fontmetrics(size=10, font=, em=) -- global metrics of the FontProvider-
821
821
  // resolved font, scaled for `size`. Mirrors _builtin_fontmetrics.
822
- Value builtinFontmetrics(Evaluator& ev, const CallArgs& args) {
823
- const double size = toDoubleLenient(getArg(args, 0, "size", Value{10.0}));
822
+ Value builtinFontmetrics(Evaluator& ev, const CallArgs& args, const oscad::Position* pos) {
823
+ const double size = textSizeArg(ev, args, 0, 2, "fontmetrics", pos);
824
824
  // Positional too: fontmetrics(10, "Liberation Sans:style=Bold") is
825
825
  // how the bug report was written, and how the real binary reads it.
826
826
  const std::string fontSpec = asStringOr(getArg(args, 1, "font", Value{std::string("")}), "");
@@ -1255,7 +1255,7 @@ Value evalBuiltinFunctionResolved(Evaluator& ev, BuiltinFnId id, const std::vect
1255
1255
  switch (id) {
1256
1256
  case BuiltinFnId::None: return Value{}; // unreachable: handled above
1257
1257
  case BuiltinFnId::TextMetrics: return builtinTextmetrics(ev, args, &node.position());
1258
- case BuiltinFnId::FontMetrics: return builtinFontmetrics(ev, args);
1258
+ case BuiltinFnId::FontMetrics: return builtinFontmetrics(ev, args, &node.position());
1259
1259
  case BuiltinFnId::Abs: return Value{std::fabs(toDoubleLenient(getArg(args, 0, "x", Value{})))};
1260
1260
  case BuiltinFnId::Sign: {
1261
1261
  const double x = toDoubleLenient(getArg(args, 0, "x", Value{}));
@@ -275,6 +275,121 @@ SphereMesh buildAligned(double r, int hsides, int vsides, bool stagger) {
275
275
  return m;
276
276
  }
277
277
 
278
+ // "octa": an octahedron with each face cut into an n x n triangular grid,
279
+ // n = ceil(segments / 4), pushed out to the sphere -- 4n^2 + 2 vertices,
280
+ // one on each pole of all three axes. Vertex placement is upstream
281
+ // OpenSCAD's (primitives.cc, SphereNode::createGeometryOcta): an interior
282
+ // vertex is where the great-circle arcs through equally spaced edge points
283
+ // meet, the three pairwise intersections averaged.
284
+ //
285
+ // The mesh is EXACTLY symmetric under all 48 symmetries of the octahedron,
286
+ // not merely to rounding: every vertex is computed once, for its
287
+ // barycentric weights sorted largest first, and the other copies are that
288
+ // one point with its coordinates permuted and negated, which is exact.
289
+ // Within a rounding error is not good enough. Manifold::Sphere, which this
290
+ // used to be, is symmetric only to ~1e-15, so a copy rotated 90 degrees
291
+ // landed a hair off the original and difference() of the two left a mass of
292
+ // sliver shards where it should leave nothing.
293
+ //
294
+ // ponytail: the great-circle vertices are not bit-identical to circle()'s
295
+ // (Manifold's Circle reduces angles by quadrant, not octant, so it is not
296
+ // exactly symmetric itself); a sphere and a coaxial circle of the same $fn
297
+ // agree to rounding only.
298
+ namespace {
299
+ // (cos, sin) of (90 * m / n) degrees, always computed from the angle at or
300
+ // below 45 degrees, so the points at m and n - m are exact mirrors.
301
+ std::array<double, 2> quarterArc(int m, int n) {
302
+ if (2 * m == n) return {std::sqrt(0.5), std::sqrt(0.5)};
303
+ if (2 * m > n) {
304
+ const std::array<double, 2> p = quarterArc(n - m, n);
305
+ return {p[1], p[0]};
306
+ }
307
+ const double t = (std::numbers::pi / 2.0) * m / n;
308
+ return {std::cos(t), std::sin(t)};
309
+ }
310
+
311
+ using V3 = std::array<double, 3>;
312
+ V3 cross(const V3& a, const V3& b) {
313
+ return {a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]};
314
+ }
315
+ V3 normalized(const V3& v) {
316
+ const double l = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
317
+ return {v[0] / l, v[1] / l, v[2] / l};
318
+ }
319
+
320
+ // The unit-sphere point with barycentric weights a >= b >= c (a + b + c = n)
321
+ // toward three orthogonal axes, as coordinates along those axes.
322
+ V3 octaCanonical(int a, int b, int c, int n) {
323
+ if (b == 0) return {1.0, 0.0, 0.0};
324
+ if (c == 0) {
325
+ const std::array<double, 2> e = quarterArc(b, n);
326
+ return {e[0], e[1], 0.0};
327
+ }
328
+ // Upstream's construction, with its i, j, k = a, b, c.
329
+ const auto ac = [&](int m) { const auto e = quarterArc(m, n); return V3{e[0], 0.0, e[1]}; };
330
+ const auto bc = [&](int m) { const auto e = quarterArc(m, n); return V3{0.0, e[0], e[1]}; };
331
+ const auto ab = [&](int m) { const auto e = quarterArc(m, n); return V3{e[0], e[1], 0.0}; };
332
+ const auto meet = [](const V3& p, const V3& q, const V3& r, const V3& s) {
333
+ V3 x = normalized(cross(cross(p, q), cross(r, s)));
334
+ if (x[0] + x[1] + x[2] < 0) x = {-x[0], -x[1], -x[2]}; // the octant's side
335
+ return x;
336
+ };
337
+ const V3 xz_k = ac(c), yz_k = bc(c);
338
+ const V3 xy_j = ab(b), yz_j = bc(n - b);
339
+ const V3 xy_i = ab(n - a), xz_i = ac(n - a);
340
+ const V3 p1 = meet(xz_k, yz_k, xy_j, yz_j);
341
+ const V3 p2 = meet(xz_k, yz_k, xy_i, xz_i);
342
+ const V3 p3 = meet(xy_j, yz_j, xy_i, xz_i);
343
+ V3 p = normalized({p1[0] + p2[0] + p3[0], p1[1] + p2[1] + p3[1], p1[2] + p2[2] + p3[2]});
344
+ // Equal weights must give equal coordinates exactly, or the two
345
+ // permutations that swap them would disagree about this one vertex.
346
+ if (a == b) p[1] = p[0];
347
+ if (b == c) p[2] = p[1];
348
+ return p;
349
+ }
350
+ } // namespace
351
+
352
+ SphereMesh buildOcta(double r, int segments) {
353
+ const int n = std::max(1, (segments + 3) / 4);
354
+ SphereMesh m;
355
+ std::map<std::array<int, 3>, int> index; // signed barycentric weights -> vertex
356
+ const auto vertex = [&](int i, int j, int k, int sx, int sy, int sz) {
357
+ const std::array<int, 3> key{sx * i, sy * j, sz * k};
358
+ auto [it, fresh] = index.try_emplace(key, static_cast<int>(m.verts.size() / 3));
359
+ if (fresh) {
360
+ const std::array<int, 3> w{i, j, k};
361
+ std::array<int, 3> axis{0, 1, 2};
362
+ std::stable_sort(axis.begin(), axis.end(), [&](int p, int q) { return w[p] > w[q]; });
363
+ const V3 c = octaCanonical(w[axis[0]], w[axis[1]], w[axis[2]], n);
364
+ V3 p{};
365
+ for (int rank = 0; rank < 3; ++rank) p[axis[rank]] = c[rank];
366
+ pushVert(m, {r * sx * p[0], r * sy * p[1], r * sz * p[2]});
367
+ }
368
+ return it->second;
369
+ };
370
+ for (int sx : {1, -1})
371
+ for (int sy : {1, -1})
372
+ for (int sz : {1, -1}) {
373
+ const bool flip = sx * sy * sz < 0;
374
+ const auto tri = [&](int a, int b, int c) {
375
+ if (flip) std::swap(b, c);
376
+ m.tris.insert(m.tris.end(), {a, b, c});
377
+ };
378
+ // "Up" triangles toward +x, +y, +z, then the "down" ones
379
+ // between them; both wind outward in the (+,+,+) octant.
380
+ for (int i = 0; i < n; ++i)
381
+ for (int j = 0; i + j < n; ++j) {
382
+ const int k = n - 1 - i - j;
383
+ tri(vertex(i + 1, j, k, sx, sy, sz), vertex(i, j + 1, k, sx, sy, sz),
384
+ vertex(i, j, k + 1, sx, sy, sz));
385
+ if (k >= 1)
386
+ tri(vertex(i, j + 1, k, sx, sy, sz), vertex(i + 1, j, k, sx, sy, sz),
387
+ vertex(i + 1, j + 1, k - 1, sx, sy, sz));
388
+ }
389
+ }
390
+ return m;
391
+ }
392
+
278
393
  // "icosa": subdivide every icosahedral face into a triangular grid and push
279
394
  // each sample out to the sphere. BOSL2 subsamples one face and rotates
280
395
  // copies onto the rest; sampling each face against its own corners is the
@@ -398,18 +513,10 @@ CSGParams resolveSphere(Evaluator& ev, const oscad::ModularCall& node, EvalConte
398
513
  params["segs"] = Value{static_cast<double>(hsides)};
399
514
  params["color"] = colorToValue(effCtx.color);
400
515
 
401
- if (style == "octa") {
402
- // Manifold::Sphere IS a subdivided octahedron (Shape::Octahedron,
403
- // then Subdivide), so this needs no mesh of our own -- generate
404
- // calls it directly.
405
- params["verts"] = Value{makeList()};
406
- params["tris"] = Value{makeList()};
407
- return params;
408
- }
409
-
410
516
  const SphereMesh m = style == "aligned" ? buildAligned(r, hsides, vsides, false)
411
517
  : style == "stagger" ? buildAligned(r, hsides, vsides, true)
412
518
  : style == "icosa" ? buildIcosa(r, hsides)
519
+ : style == "octa" ? buildOcta(r, hsides)
413
520
  : buildOrig(r, hsides, vsides);
414
521
 
415
522
  std::vector<Value> vertsValues;
@@ -433,11 +540,6 @@ std::vector<ColoredBody> generateSphere(Evaluator& ev, const CSGParams& params,
433
540
  for (const Value& t : std::get<ListPtr>(params.at("tris"))->items) {
434
541
  mesh.triVerts.push_back(static_cast<uint64_t>(std::get<double>(t)));
435
542
  }
436
- if (std::get<std::string>(params.at("style")) == "octa") {
437
- manifold::Manifold octa = manifold::Manifold::Sphere(
438
- std::get<double>(params.at("r")), static_cast<int>(std::get<double>(params.at("segs"))));
439
- return {ev.tagGenerated(std::move(octa), node, params.at("color"))};
440
- }
441
543
  manifold::Manifold body(mesh);
442
544
  return {ev.tagGenerated(std::move(body), node, params.at("color"))};
443
545
  }
@@ -138,14 +138,14 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
138
138
  {"levelset", {"field", "bounds", "isovalue", "invert", "edge"}},
139
139
  {"offset", {"r", "delta", "chamfer"}},
140
140
  {"surface", {"file", "center", "convexity", "invert"}},
141
- {"text", {"text", "size", "font", "direction", "language", "script", "halign", "valign", "spacing"}},
141
+ {"text", {"text", "size", "font", "direction", "language", "script", "halign", "valign", "spacing", "em"}},
142
142
  // breakpoint() is this port's own debugger extension, no upstream
143
143
  // equivalent to mirror.
144
144
  {"breakpoint", {"condition"}},
145
145
 
146
146
  // -- the three builtin FUNCTIONS that do use Parameters::parse ----
147
- {"textmetrics", {"text", "size", "font", "direction", "language", "script", "halign", "valign", "spacing"}},
148
- {"fontmetrics", {"size", "font"}},
147
+ {"textmetrics", {"text", "size", "font", "direction", "language", "script", "halign", "valign", "spacing", "em"}},
148
+ {"fontmetrics", {"size", "font", "em"}},
149
149
  };
150
150
  auto it = table.find(name);
151
151
  return it == table.end() ? nullptr : &it->second;
@@ -7,7 +7,7 @@
7
7
 
8
8
  #include <algorithm>
9
9
 
10
- // text(text=, size=10, font=, halign="default", valign="default", spacing=1,
10
+ // text(text=, size=10 | em=, font=, halign="default", valign="default", spacing=1,
11
11
  // direction=, language=, script=) -- renders `text` as 2D glyph outlines
12
12
  // through the FontProvider (Evaluator::fontProvider(), lazily the built-in
13
13
  // FreetypeFontProvider if none was injected).
@@ -30,10 +30,24 @@ std::string asStringOr(const Value& v, const std::string& fallback) {
30
30
  }
31
31
  } // namespace
32
32
 
33
+ double textSizeArg(Evaluator& ev, const CallArgs& args, std::optional<int> sizePos, std::optional<int> emPos,
34
+ const char* caller, const oscad::Position* where) {
35
+ const Value em = getArg(args, emPos, "em");
36
+ const Value size = getArg(args, sizePos, "size");
37
+ if (std::holds_alternative<std::monostate>(em)) {
38
+ return std::holds_alternative<std::monostate>(size) ? 10.0 : toDoubleLenient(size);
39
+ }
40
+ if (!std::holds_alternative<std::monostate>(size)) {
41
+ ev.warn(std::string(caller) + ": \"size\" ignored when \"em\" is set", where);
42
+ }
43
+ return toDoubleLenient(em) * 72.0 / 100.0;
44
+ }
45
+
33
46
  CSGParams resolveText(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
34
47
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
35
48
  const std::string text = asStringOr(getArg(args, 0, "text", Value{std::string("")}), "");
36
- const double size = toDoubleLenient(getArg(args, 1, "size", Value{10.0}));
49
+ // em is name-only, like everything after font here.
50
+ const double size = textSizeArg(ev, args, 1, std::nullopt, "text", &node.position());
37
51
  // font is POSITIONAL, at index 2: `text("Hi", 10, "Liberation Sans")`
38
52
  // is how people write it and how the reference reads it -- measured
39
53
  // against the 2026.02.01 binary, whose bounding box for the
@@ -40,9 +40,10 @@ std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodi
40
40
  // slab finds the node that built the shape (see
41
41
  // ColoredBody::sectionId). Manifold keeps a MeshGL's
42
42
  // runOriginalID when it is given one.
43
- manifold::MeshGL mesh = flat.body->GetMeshGL();
43
+ // MeshGL64: rebuilt below, and float32 would move every vertex.
44
+ manifold::MeshGL64 mesh = flat.body->GetMeshGL64();
44
45
  mesh.runOriginalID = {*cb.sectionId};
45
- mesh.runIndex = {0, static_cast<uint32_t>(mesh.triVerts.size())};
46
+ mesh.runIndex = {0, mesh.triVerts.size()};
46
47
  mesh.runTransform.clear();
47
48
  mesh.faceID.clear();
48
49
  flat.body = manifold::Manifold(mesh);
@@ -364,7 +364,7 @@ std::shared_ptr<const std::vector<ColoredBody>> remapParts(const std::vector<Col
364
364
  for (ColoredBody part : parts) {
365
365
  if (part.mergedFrom) part.mergedFrom = remapParts(*part.mergedFrom, remap);
366
366
  if (part.body && !part.body->IsEmpty()) {
367
- manifold::MeshGL mesh = part.body->GetMeshGL();
367
+ manifold::MeshGL64 mesh = part.body->GetMeshGL64();
368
368
  for (uint32_t& id : mesh.runOriginalID) {
369
369
  auto found = remap.find(id);
370
370
  if (found != remap.end()) id = found->second;
@@ -417,7 +417,14 @@ void Evaluator::restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::
417
417
  cb.sectionId = fresh;
418
418
  }
419
419
  if (!cb.body || cb.body->IsEmpty()) continue;
420
- manifold::MeshGL mesh = cb.body->GetMeshGL();
420
+ // MeshGL64, not MeshGL: the body is rebuilt from this mesh below,
421
+ // and MeshGL is float32. Rounding every vertex made a cache hit a
422
+ // slightly different solid from the same shape built fresh, so
423
+ // difference() { sphere(50); sphere(50); } left a shell of slivers
424
+ // wherever one operand came from the cache -- only in the GUI,
425
+ // which has a cache, never in the CLI. A cube survived because its
426
+ // coordinates are exact in float32.
427
+ manifold::MeshGL64 mesh = cb.body->GetMeshGL64();
421
428
  if (mesh.runOriginalID.empty()) continue;
422
429
  std::unordered_map<uint32_t, uint32_t> remap;
423
430
  // fresh ID -> the colour its triangles carried, multi-colour
@@ -496,3 +496,26 @@ TEST(ManifoldCache, AHitOnAQuietSubtreeStaysQuiet) {
496
496
  EXPECT_EQ(countLines(), 0);
497
497
  EXPECT_EQ(countLines(), 0);
498
498
  }
499
+
500
+ // A cache hit used to come back rebuilt through float32 (MeshGL), so the
501
+ // second of two identical spheres was a slightly different solid from the
502
+ // first and difference() left a shell of slivers -- in the GUI only, since
503
+ // the CLI has no cache. A cube hid it: its coordinates are exact in float.
504
+ // Covers the hit within one render, across renders, and the keep-minuend
505
+ // path, which rebuilt its parts the same way.
506
+ TEST(ManifoldCache, AHitIsTheSameSolidNotAFloatRoundedCopy) {
507
+ const std::string srcs[] = {
508
+ "difference() { sphere(d=100, $fn=16); sphere(d=100, $fn=16); }",
509
+ "difference() { sphere(d=100, style=\"octa\", $fn=16); sphere(d=100, style=\"octa\", $fn=16); }",
510
+ "difference() { rotate([10, 20, 30]) cylinder(h=7.3, r=3.1); rotate([10, 20, 30]) cylinder(h=7.3, r=3.1); }",
511
+ };
512
+ for (const std::string& src : srcs) {
513
+ auto cache = std::make_shared<ManifoldCache>();
514
+ for (int render = 0; render < 2; ++render) {
515
+ Evaluated e = evalSrcWithCache(src, cache);
516
+ for (const ColoredBody& b : e.bodies) {
517
+ EXPECT_TRUE(!b.body || b.body->IsEmpty()) << src << " render " << render;
518
+ }
519
+ }
520
+ }
521
+ }
@@ -3,7 +3,11 @@
3
3
 
4
4
  #include "test_helpers.hpp"
5
5
 
6
+ #include <algorithm>
7
+ #include <array>
6
8
  #include <cmath>
9
+ #include <set>
10
+ #include <string>
7
11
  #include <gtest/gtest.h>
8
12
  #include <numbers>
9
13
 
@@ -398,14 +402,45 @@ TEST(SphereStyle, EveryStyleMatchesBosl2sCounts) {
398
402
  }
399
403
 
400
404
  TEST(SphereStyle, EveryStyleMatchesBosl2sVolume) {
401
- // aligned/stagger/icosa agree with BOSL2 to the last digit. "octa" comes
402
- // from Manifold::Sphere, whose octahedral subdivision distributes its
403
- // vertices a little differently than BOSL2's -- same 146/288 topology,
404
- // ~0.05% apart in volume.
405
+ // aligned/stagger/icosa agree with BOSL2 to the last digit. "octa" is
406
+ // upstream OpenSCAD's construction (sphere(style="octa"), 2026-09), not
407
+ // BOSL2's -- same 146/288 topology. 4026.4886 is the hull volume of an
408
+ // independent port of upstream's createGeometryOcta.
405
409
  EXPECT_NEAR(sphereStats("sphere(r=10, style=\"aligned\", $fn=24);").volume, 4070.5524, 1e-3);
406
410
  EXPECT_NEAR(sphereStats("sphere(r=10, style=\"stagger\", $fn=24);").volume, 4082.1246, 1e-3);
407
411
  EXPECT_NEAR(sphereStats("sphere(r=10, style=\"icosa\", $fn=24);").volume, 4097.2467, 1e-3);
408
- EXPECT_NEAR(sphereStats("sphere(r=10, style=\"octa\", $fn=24);").volume, 4024.3239, 1e-3);
412
+ EXPECT_NEAR(sphereStats("sphere(r=10, style=\"octa\", $fn=24);").volume, 4026.4886, 1e-3);
413
+ }
414
+
415
+ // "octa" must be EXACTLY symmetric under the octahedron's 48 symmetries, not
416
+ // to rounding: a copy rotated 90 degrees otherwise lands a hair off the
417
+ // original, and difference() of the two leaves a mass of sliver shards
418
+ // instead of nothing. It used to be Manifold::Sphere, symmetric to ~1e-15.
419
+ TEST(SphereStyle, OctaIsExactlyOctahedrallySymmetric) {
420
+ for (int fn : {3, 8, 16, 21, 37, 64}) {
421
+ const std::string sphere = "sphere(d=100, style=\"octa\", $fn=" + std::to_string(fn) + ")";
422
+ Evaluated e = evalSrc(sphere + ";");
423
+ const manifold::MeshGL64 mesh = e.bodies[0].body->GetMeshGL64();
424
+ const size_t n = static_cast<size_t>(std::max(1, (fn + 3) / 4));
425
+ EXPECT_EQ(mesh.vertProperties.size() / 3, 4 * n * n + 2) << fn;
426
+ std::set<std::array<double, 3>> verts;
427
+ for (size_t v = 0; v < mesh.vertProperties.size(); v += 3)
428
+ verts.insert({mesh.vertProperties[v] + 0.0, mesh.vertProperties[v + 1] + 0.0, mesh.vertProperties[v + 2] + 0.0});
429
+ std::array<int, 3> perm{0, 1, 2};
430
+ do {
431
+ for (int signs = 0; signs < 8; ++signs) {
432
+ for (const std::array<double, 3>& p : verts) {
433
+ std::array<double, 3> q{};
434
+ for (int a = 0; a < 3; ++a) q[a] = ((signs >> a) & 1 ? -p[perm[a]] : p[perm[a]]) + 0.0;
435
+ EXPECT_TRUE(verts.count(q)) << fn;
436
+ }
437
+ }
438
+ } while (std::next_permutation(perm.begin(), perm.end()));
439
+ for (const char* rot : {"[90,0,0]", "[0,90,0]", "[0,0,90]", "[180,0,0]"}) {
440
+ Evaluated d = evalSrc("difference() { " + sphere + "; rotate(" + rot + ") " + sphere + "; }");
441
+ for (const ColoredBody& b : d.bodies) EXPECT_TRUE(!b.body || b.body->IsEmpty()) << fn << " " << rot;
442
+ }
443
+ }
409
444
  }
410
445
 
411
446
  TEST(SphereStyle, EveryStyleIsAClosedSolidUnderTheTrueVolume) {
@@ -192,6 +192,36 @@ TEST(Textmetrics, MultiByteUtf8CodepointsDecodeWithoutCrashing) {
192
192
  EXPECT_GT(asNum(items[0]), 0.0);
193
193
  }
194
194
 
195
+ // em (upstream 2026-03, openscad #4304): `em` sets the em square, i.e.
196
+ // size = em * 72/100, and wins over `size` with a warning. Positional at
197
+ // index 9 in textmetrics and 2 in fontmetrics, as upstream's parameter
198
+ // lists put it; text() takes it by name only, like its other options.
199
+ TEST(Text, EmIsSizeTimes72Over100) {
200
+ const std::pair<const char*, std::vector<std::string>> cases[] = {
201
+ {"echo(str(textmetrics(\"Hi\", em=10)) == str(textmetrics(\"Hi\", size=7.2)));", {"ECHO: true"}},
202
+ {"echo(str(textmetrics(\"Hi\", 3, \"\", \"ltr\", \"en\", \"latin\", \"left\", \"baseline\", 1, 10))"
203
+ " == str(textmetrics(\"Hi\", size=7.2)));",
204
+ {"WARNING: textmetrics: \"size\" ignored when \"em\" is set in file <string>, line 1", "ECHO: true"}},
205
+ {"echo(str(fontmetrics(em=10)) == str(fontmetrics(size=7.2)));", {"ECHO: true"}},
206
+ // Inside a function, so the compiled (VM) call path is covered too.
207
+ {"function f() = textmetrics(\"Hi\", em=10); echo(str(f()) == str(textmetrics(\"Hi\", size=7.2)));",
208
+ {"ECHO: true"}},
209
+ {"echo(str(fontmetrics(undef, \"\", 10)) == str(fontmetrics(size=7.2)));", {"ECHO: true"}},
210
+ {"m = fontmetrics(size=3, em=10);",
211
+ {"WARNING: fontmetrics: \"size\" ignored when \"em\" is set in file <string>, line 1"}},
212
+ {"text(\"Hi\", size=3, em=10);",
213
+ {"WARNING: text: \"size\" ignored when \"em\" is set in file <string>, line 1"}},
214
+ };
215
+ for (const auto& [src, want] : cases) {
216
+ std::vector<std::string> log;
217
+ evalSrc(src, [&](const std::string& m) { log.push_back(m); });
218
+ EXPECT_EQ(log, want) << src;
219
+ }
220
+ Evaluated em = evalSrc("text(\"A\", em=10, $fn=32);");
221
+ Evaluated size = evalSrc("text(\"A\", size=7.2, $fn=32);");
222
+ EXPECT_NEAR(em.bodies[0].section->Area(), size.bodies[0].section->Area(), 1e-9);
223
+ }
224
+
195
225
  // -- fontmetrics() --------------------------------------------------------
196
226
 
197
227
  TEST(Fontmetrics, ReturnsAllExpectedKeysInOrder) {
@@ -55,7 +55,7 @@ wheels = [
55
55
 
56
56
  [[package]]
57
57
  name = "openscad-cpp-evaluator"
58
- version = "1.38.1"
58
+ version = "1.40.0"
59
59
  source = { editable = "." }
60
60
  dependencies = [
61
61
  { name = "numpy" },