openscad-cpp-evaluator 0.45.1__tar.gz → 0.47.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 (208) hide show
  1. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/CLAUDE.md +18 -5
  2. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/bindings/module.cpp +4 -0
  4. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/export.hpp +7 -0
  5. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/python/openscad_cpp_evaluator/__init__.py +8 -2
  7. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/bytecode_compiler.cpp +27 -7
  8. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/export.cpp +89 -2
  9. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/user_calls.cpp +26 -16
  10. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_export.cpp +85 -0
  11. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_function_builtins.cpp +92 -11
  12. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/.github/workflows/ci.yml +0 -0
  13. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/.github/workflows/wheels.yml +0 -0
  14. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/.gitignore +0 -0
  15. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/.gitmodules +0 -0
  16. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/CMakeLists.txt +0 -0
  17. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/LICENSE +0 -0
  18. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/README.md +0 -0
  19. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/bindings/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/bindings/ast_to_py.cpp +0 -0
  21. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/cmake/embed_font.cmake +0 -0
  22. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/examples/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/examples/eval_perf_benchmark.cpp +0 -0
  24. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/examples/manifold_cache_reuse.cpp +0 -0
  25. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/examples/minimal_debugger.cpp +0 -0
  26. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  27. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/.gitignore +0 -0
  28. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  29. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  30. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/README.md +0 -0
  31. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  32. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  33. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  34. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  35. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  36. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  37. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  38. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  39. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  40. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  41. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  42. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  43. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  44. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  45. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  46. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  47. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  48. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  49. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  50. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  51. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  52. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  53. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  54. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  55. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  56. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  57. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  58. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  59. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  60. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  61. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  62. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  63. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  64. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  65. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  66. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  67. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  68. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  69. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  70. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  71. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  72. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  73. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  74. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  75. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  76. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  77. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  78. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  79. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  80. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  81. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  82. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  83. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  84. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  85. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  86. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  87. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  88. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  89. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  90. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  91. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  92. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  93. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  94. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  95. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  96. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  97. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  98. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  99. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  100. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  101. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  102. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  103. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  104. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  105. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  106. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  107. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  108. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  109. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  110. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  111. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  112. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  113. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  114. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  115. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  116. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  117. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  118. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  119. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  120. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  121. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  122. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  123. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  124. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  125. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/resources/fonts/LICENSE +0 -0
  126. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  127. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/CMakeLists.txt +0 -0
  128. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/booleans.cpp +0 -0
  129. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/builtins.hpp +0 -0
  130. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/call_args.cpp +0 -0
  131. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/color.cpp +0 -0
  132. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/control.cpp +0 -0
  133. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/dxf_dim.cpp +0 -0
  134. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/extrude.cpp +0 -0
  135. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/function_builtins.cpp +0 -0
  136. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/import.cpp +0 -0
  137. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/modifiers.cpp +0 -0
  138. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/primitives_2d.cpp +0 -0
  139. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/primitives_3d.cpp +0 -0
  140. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/registry.cpp +0 -0
  141. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/roof.cpp +0 -0
  142. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/segments.cpp +0 -0
  143. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/surface.cpp +0 -0
  144. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/text.cpp +0 -0
  145. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/topology.cpp +0 -0
  146. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/builtins/transforms.cpp +0 -0
  147. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/bytecode_vm.cpp +0 -0
  148. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/colored_body.cpp +0 -0
  149. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/csg_generate.cpp +0 -0
  150. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/csg_resolve.cpp +0 -0
  151. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/css_colors.cpp +0 -0
  152. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/debug/debug_repl.cpp +0 -0
  153. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/debug_profile.cpp +0 -0
  154. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/eval_context.cpp +0 -0
  155. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/eval_error.cpp +0 -0
  156. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/eval_use.cpp +0 -0
  157. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/expr_eval.cpp +0 -0
  158. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/import/dxf_import.cpp +0 -0
  159. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/import/mesh_import.cpp +0 -0
  160. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/import/surface_load.cpp +0 -0
  161. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/import/svg_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/manifold_cache.cpp +0 -0
  163. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/measure_geometry.cpp +0 -0
  164. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/mesh_check.cpp +0 -0
  165. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/stmt_eval.cpp +0 -0
  166. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/text/font_match.cpp +0 -0
  167. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/text/freetype_font_provider.cpp +0 -0
  168. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/text/text_metrics.cpp +0 -0
  169. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/value.cpp +0 -0
  170. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/src/zip_stored.cpp +0 -0
  171. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/CMakeLists.txt +0 -0
  172. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_booleans.cpp +0 -0
  173. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_bytecode_compiler.cpp +0 -0
  174. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_children_separate.cpp +0 -0
  175. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_cli.cpp +0 -0
  176. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_control_flow.cpp +0 -0
  177. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_csg_tree.cpp +0 -0
  178. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_debug_hooks.cpp +0 -0
  179. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_dxf_svg_import.cpp +0 -0
  180. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_eval_context.cpp +0 -0
  181. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_expr_eval.cpp +0 -0
  182. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_extrude_roof.cpp +0 -0
  183. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_feature_detection.cpp +0 -0
  184. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_helpers.hpp +0 -0
  185. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_import_export.cpp +0 -0
  186. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_manifold_cache.cpp +0 -0
  187. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_mesh_check.cpp +0 -0
  188. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_multi_color_merge.cpp +0 -0
  189. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_primitives.cpp +0 -0
  190. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_profiling.cpp +0 -0
  191. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_python_bindings.py +0 -0
  192. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_render_expr.cpp +0 -0
  193. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_scope_trail.cpp +0 -0
  194. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_scoping.cpp +0 -0
  195. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_surface.cpp +0 -0
  196. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_tail_calls.cpp +0 -0
  197. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_text.cpp +0 -0
  198. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_transforms.cpp +0 -0
  199. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_unexpected_args.cpp +0 -0
  200. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_use.cpp +0 -0
  201. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_value.cpp +0 -0
  202. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_viewport_params.cpp +0 -0
  203. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tests/test_zip_stored.cpp +0 -0
  204. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tools/cli/CMakeLists.txt +0 -0
  205. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tools/cli/cli_lib.cpp +0 -0
  206. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tools/cli/cli_lib.hpp +0 -0
  207. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/tools/cli/main.cpp +0 -0
  208. {openscad_cpp_evaluator-0.45.1 → openscad_cpp_evaluator-0.47.0}/uv.lock +0 -0
@@ -45,9 +45,13 @@ comprehension clause kinds, user function calls (recursion, defaults, `FunctionL
45
45
  user module calls (`children()`/`children(N)`, closure detection via `callCtxFor`), list
46
46
  indexing/swizzle member access; the full math/string/list/type-check builtin *function* dispatch
47
47
  (`sin`/`cos`/`pow`/`search`/`lookup`/`chr`/`ord`/`is_*`/`object`/`has_key`/etc, ~43 names — a
48
- completely separate namespace/precedence rule from the builtin *modules* in `registry.cpp`: a
49
- builtin function name always wins over a same-named user function, checked *before* the
50
- user-function lookup), STL/OBJ/OFF/3MF `import()` in both module (geometry) and expression
48
+ same precedence rule as the builtin *modules* in `registry.cpp`: a user-defined function
49
+ **shadows** a same-named builtin, looked up *before* the builtin table. This was backwards until
50
+ 0.47.0 — the port copied the old Python evaluator's `_eval_function_call` order, which had it
51
+ wrong; OpenSCAD 2026.02.01 echoes 999 for `function abs(x)=999; abs(-3)` and we echoed 3. Fixing
52
+ it needs **both** resolution sites: `evalFunctionCall` (`user_calls.cpp`) and the compiler's
53
+ call-site resolution (`bytecode_compiler.cpp`), which bakes builtin-vs-user in at compile time,
54
+ plus gating the silent `is_undef` probe on the call actually resolving to the builtin), STL/OBJ/OFF/3MF `import()` in both module (geometry) and expression
51
55
  (VNF/JSON) contexts, and 3MF read/write; (Phase 6) `hull()`/`minkowski()` (transparent-splice
52
56
  CSG-tree shape like union/difference/intersection, role-split via the shared `splitByRole`),
53
57
  `linear_extrude()`/`rotate_extrude()`/`projection(cut=)`, and `roof()`; and (Phase 7) `offset()`,
@@ -711,8 +715,17 @@ grep for `ponytail:`.
711
715
  — skip it and the mesh comes out non-manifold at the seam). Self-contained 2D geometry math here
712
716
  (`dot2`/`len2`/`norm2`, the parabola-canonical-frame rotation in `discretizeArc`) exists nowhere
713
717
  else in this codebase.
714
- - `include/openscad_cpp_evaluator/export.hpp`, `src/export.cpp` — binary STL, OBJ, OFF, and 3MF
715
- writers (compose every body into one solid first; 3MF via `zip_stored.hpp`'s `writeDeflateZip`).
718
+ - `include/openscad_cpp_evaluator/export.hpp`, `src/export.cpp` — the writers for all eight
719
+ formats `exportExtensions()` lists: 3MF, AMF, STL, OBJ, OFF, PLY, VRML, X3D (3MF via
720
+ `zip_stored.hpp`'s `writeDeflateZip`). **`exportExtensions()` is the one source of truth for
721
+ which formats exist** — the Python facade and BelfrySCAD's dialog/CLI all ask it rather than
722
+ restating the list, because three hand-written copies had already drifted far enough that `.off`
723
+ could be written by neither the GUI nor the CLI while every test passed.
724
+ Colour is carried by 3MF, AMF, OBJ (companion `.mtl`), PLY, VRML and X3D; STL and OFF compose
725
+ every body into one solid and store geometry only. **AMF** puts colour on the `<volume>` rather
726
+ than the face, so an object whose triangles are not all one colour is written as one volume per
727
+ colour — per-triangle `<color>` is legal in the spec but poorly supported by the slicers that are
728
+ the audience for it.
716
729
  `colored_body.hpp`/`.cpp`'s `toRenderableBodies()` (thin-extrudes any top-level 2D-only result to
717
730
  a 1e-3-unit-tall `flatPreview` Manifold) must run on a body list before handing it to any of these
718
731
  writers — none of them look at `ColoredBody::section` at all, only `.body`. `tools/cli/cli_lib.cpp`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.45.1
3
+ Version: 0.47.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -786,6 +786,10 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
786
786
  "logging them. Raises RuntimeError when there is no geometry, the format is unknown, or the "
787
787
  "file cannot be opened.");
788
788
 
789
+ m.def("export_extensions", []() { return oscadeval::exportExtensions(); },
790
+ "The file extensions export_model understands, dot-prefixed. Read from the C++ writer table "
791
+ "rather than restated here, so the Python side cannot drift from what can actually be written.");
792
+
789
793
  m.def("evaluate", &evaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("manifold_cache") = nullptr,
790
794
  nb::arg("profile") = false,
791
795
  "Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit).");
@@ -97,6 +97,13 @@ void writeVrml(const std::string& path, const std::vector<ExportObject>& objects
97
97
  // Color) and Shape level 1 (Appearance, Material), exactly the nodes used.
98
98
  void writeX3d(const std::string& path, const std::vector<ExportObject>& objects);
99
99
 
100
+ // AMF 1.1. XML like X3D, but its colour lives on the <volume>, not the
101
+ // face: a mesh holds one vertex list and one <volume> per material. An
102
+ // object whose triangles are not all one colour therefore becomes several
103
+ // volumes -- which is what a multimaterial slicer reads as separate
104
+ // materials to assign to tools.
105
+ void writeAmf(const std::string& path, const std::vector<ExportObject>& objects);
106
+
100
107
  // OFF (Object File Format): header "OFF", "$verts $tris 0", vertex lines,
101
108
  // then "3 i j k" per triangle (0-indexed, count-prefixed). Mirrors
102
109
  // export.py's write_off.
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.45.1"
7
+ version = "0.47.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -315,8 +315,14 @@ def export_model(path: str, geometry, format: str = "", ascii_stl: bool = False,
315
315
 
316
316
 
317
317
  def export_extensions() -> list:
318
- """The file extensions export_model understands, dot-prefixed."""
319
- return [".3mf", ".stl", ".obj", ".off", ".ply", ".wrl", ".x3d"]
318
+ """The file extensions export_model understands, dot-prefixed.
319
+
320
+ Asked of the extension rather than restated here. The hardcoded copy
321
+ this replaces had already drifted: it was missing formats the C++
322
+ writer table had gained, and nothing failed -- callers simply could
323
+ not reach them.
324
+ """
325
+ return list(_ext.export_extensions())
320
326
 
321
327
 
322
328
  def check_mesh(verts, tris) -> dict:
@@ -567,6 +567,19 @@ public:
567
567
  // evalBuiltinFunction when isImport is set.
568
568
  site.isImport = true;
569
569
  isStatic = true;
570
+ } else if (const oscad::ASTNode* userFn =
571
+ scope_ ? scope_->lookupFunction(calleeName) : nullptr;
572
+ userFn && userFn->kind() == NodeKind::FunctionDeclaration) {
573
+ // A user-defined function SHADOWS a same-named
574
+ // builtin, so this lookup runs BEFORE the builtin
575
+ // table -- the same reordering as
576
+ // evalFunctionCall's (user_calls.cpp), and it has
577
+ // to happen here too: this decision is baked in at
578
+ // COMPILE time, so leaving the builtin test first
579
+ // would keep the bug on the default engine no
580
+ // matter what the interpreter does.
581
+ site.decl = static_cast<const oscad::FunctionDeclaration*>(userFn);
582
+ isStatic = true;
570
583
  } else if (isBuiltinFunctionName(calleeName)) {
571
584
  // object() is already in this table -- it gets
572
585
  // isBuiltin=true here like any other builtin, and
@@ -578,13 +591,10 @@ public:
578
591
  // split positional/named CallArgs shape).
579
592
  site.isBuiltin = true;
580
593
  isStatic = true;
581
- } else {
582
- const oscad::ASTNode* found = scope_ ? scope_->lookupFunction(calleeName) : nullptr;
583
- if (found && found->kind() == NodeKind::FunctionDeclaration) {
584
- site.decl = static_cast<const oscad::FunctionDeclaration*>(found);
585
- isStatic = true;
586
- }
587
594
  }
595
+ // Anything else stays dynamic: a function-literal value,
596
+ // or a name that is simply unknown. Op::CallDynamic
597
+ // resolves it (and warns) at runtime.
588
598
  }
589
599
 
590
600
  if (!isStatic) {
@@ -626,7 +636,17 @@ public:
626
636
  // is_undef(name) probes rather than reads -- the same quiet
627
637
  // load the interpreter uses (Evaluator::undefProbeTarget),
628
638
  // so the two engines agree on which reads warn.
629
- if (const oscad::Identifier* undefProbe = Evaluator::undefProbeTarget(n)) {
639
+ //
640
+ // Only when the call resolves to the BUILTIN, though. A
641
+ // user function named is_undef shadows it (site resolution
642
+ // above) and evaluates its argument the ordinary way,
643
+ // warnings included -- which is what OpenSCAD does. The
644
+ // interpreter gets this for free by testing the probe
645
+ // inside its builtin branch; here the two are separate, so
646
+ // the condition has to be spelled out.
647
+ const oscad::Identifier* undefProbe =
648
+ site.isBuiltin ? Evaluator::undefProbeTarget(n) : nullptr;
649
+ if (undefProbe) {
630
650
  compileIdentifierLoad(undefProbe->name, undefProbe->position(), out, scope,
631
651
  /*warnIfUndef=*/false);
632
652
  site.argNames.push_back(std::nullopt);
@@ -764,6 +764,91 @@ void writeX3d(const std::string& path, const std::vector<ExportObject>& objects)
764
764
  out << " </Scene>\n</X3D>\n";
765
765
  }
766
766
 
767
+ // AMF is XML like X3D, but its colour model is per-VOLUME rather than
768
+ // per-face: a mesh carries one <vertices> list and any number of <volume>
769
+ // blocks, each naming a material. So an object whose triangles are not all
770
+ // one colour is written as one volume per distinct colour -- <color> on an
771
+ // individual <triangle> is legal but far less widely read, and slicers are
772
+ // the audience here.
773
+ //
774
+ // Material and object ids start at 1; id 0 is reserved by the spec.
775
+ // The material table is global, so two objects sharing a colour share one
776
+ // material entry rather than each declaring its own.
777
+ void writeAmf(const std::string& path, const std::vector<ExportObject>& objects) {
778
+ std::ofstream out(path);
779
+ if (!out) throw std::runtime_error("Could not open '" + path + "' for writing");
780
+
781
+ std::vector<std::array<float, 4>> materials;
782
+ const auto materialFor = [&](const std::array<float, 4>& c) -> size_t {
783
+ for (size_t i = 0; i < materials.size(); ++i) {
784
+ if (materials[i] == c) return i + 1;
785
+ }
786
+ materials.push_back(c);
787
+ return materials.size();
788
+ };
789
+
790
+ struct Volume {
791
+ size_t material = 1;
792
+ std::vector<uint32_t> tris;
793
+ };
794
+ std::vector<std::vector<Volume>> perObject;
795
+ perObject.reserve(objects.size());
796
+ for (const ExportObject& o : objects) {
797
+ const FaceColors fc = faceColors(o);
798
+ std::vector<Volume> vols;
799
+ if (fc.palette.empty()) {
800
+ Volume v;
801
+ v.material = materialFor(o.color);
802
+ v.tris = o.tris;
803
+ vols.push_back(std::move(v));
804
+ } else {
805
+ vols.resize(fc.palette.size());
806
+ for (size_t i = 0; i < fc.palette.size(); ++i) vols[i].material = materialFor(fc.palette[i]);
807
+ for (size_t t = 0; t < o.tris.size() / 3; ++t) {
808
+ std::vector<uint32_t>& dst = vols[fc.index[t]].tris;
809
+ dst.push_back(o.tris[t * 3]);
810
+ dst.push_back(o.tris[t * 3 + 1]);
811
+ dst.push_back(o.tris[t * 3 + 2]);
812
+ }
813
+ }
814
+ perObject.push_back(std::move(vols));
815
+ }
816
+
817
+ out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
818
+ out << "<amf unit=\"millimeter\" version=\"1.1\">\n";
819
+ out << " <metadata type=\"cad\">BelfrySCAD</metadata>\n";
820
+ for (size_t i = 0; i < materials.size(); ++i) {
821
+ const std::array<float, 4>& c = materials[i];
822
+ out << " <material id=\"" << (i + 1) << "\">\n";
823
+ out << " <color><r>" << formatG6(c[0]) << "</r><g>" << formatG6(c[1]) << "</g><b>"
824
+ << formatG6(c[2]) << "</b>";
825
+ if (c[3] < 1.0f) out << "<a>" << formatG6(c[3]) << "</a>";
826
+ out << "</color>\n";
827
+ out << " </material>\n";
828
+ }
829
+ for (size_t oi = 0; oi < objects.size(); ++oi) {
830
+ const ExportObject& o = objects[oi];
831
+ out << " <object id=\"" << (oi + 1) << "\">\n <mesh>\n <vertices>\n";
832
+ for (size_t v = 0; v < o.verts.size() / 3; ++v) {
833
+ out << " <vertex><coordinates><x>" << formatG6(o.verts[v * 3]) << "</x><y>"
834
+ << formatG6(o.verts[v * 3 + 1]) << "</y><z>" << formatG6(o.verts[v * 3 + 2])
835
+ << "</z></coordinates></vertex>\n";
836
+ }
837
+ out << " </vertices>\n";
838
+ for (const Volume& vol : perObject[oi]) {
839
+ if (vol.tris.empty()) continue;
840
+ out << " <volume materialid=\"" << vol.material << "\">\n";
841
+ for (size_t t = 0; t < vol.tris.size() / 3; ++t) {
842
+ out << " <triangle><v1>" << vol.tris[t * 3] << "</v1><v2>" << vol.tris[t * 3 + 1]
843
+ << "</v2><v3>" << vol.tris[t * 3 + 2] << "</v3></triangle>\n";
844
+ }
845
+ out << " </volume>\n";
846
+ }
847
+ out << " </mesh>\n </object>\n";
848
+ }
849
+ out << "</amf>\n";
850
+ }
851
+
767
852
 
768
853
  // -- one entry point ------------------------------------------------------
769
854
 
@@ -779,7 +864,7 @@ std::string lowerExtension(const std::string& path) {
779
864
  }
780
865
 
781
866
  bool isMultiObject(const std::string& ext) {
782
- return ext == ".3mf" || ext == ".obj" || ext == ".ply" || ext == ".wrl" || ext == ".x3d";
867
+ return ext == ".3mf" || ext == ".amf" || ext == ".obj" || ext == ".ply" || ext == ".wrl" || ext == ".x3d";
783
868
  }
784
869
 
785
870
  // The merged single mesh STL/OFF write, with the open shells kept.
@@ -898,7 +983,7 @@ void writeOffMesh(const std::string& path, const manifold::MeshGL& mesh) {
898
983
  } // namespace
899
984
 
900
985
  const std::vector<std::string>& exportExtensions() {
901
- static const std::vector<std::string> exts = {".3mf", ".stl", ".obj", ".off", ".ply", ".wrl", ".x3d"};
986
+ static const std::vector<std::string> exts = {".3mf", ".amf", ".stl", ".obj", ".off", ".ply", ".wrl", ".x3d"};
902
987
  return exts;
903
988
  }
904
989
 
@@ -929,6 +1014,8 @@ std::vector<std::string> exportModel(const std::string& path, const std::vector<
929
1014
  if (objects.empty()) throw std::runtime_error("No geometry to export");
930
1015
  if (ext == ".3mf") {
931
1016
  writeThreeMf(path, objects);
1017
+ } else if (ext == ".amf") {
1018
+ writeAmf(path, objects);
932
1019
  } else if (ext == ".obj") {
933
1020
  writeObj(path, objects);
934
1021
  } else if (ext == ".ply") {
@@ -876,11 +876,32 @@ Value Evaluator::evalFunctionCall(const oscad::PrimaryCall& node, EvalContext& c
876
876
  CallArgs args = resolveArgs(*this, node.arguments, ctx);
877
877
  return importAsValue(*this, args, node);
878
878
  }
879
- // A builtin function name always wins over a same-named user
880
- // function -- checked *before* the user-function lookup, not
881
- // after, matching the reference's _eval_function_call precedence
882
- // exactly (name in _BUILTIN_FN_NAMES gates which branch runs at
883
- // all).
879
+ // A user-defined function SHADOWS a same-named builtin, so the
880
+ // user lookup runs FIRST. Verified against OpenSCAD 2026.02.01:
881
+ // `function norm(x) = 999; echo(norm([3,4]));` echoes 999 there,
882
+ // and 5 here until this was fixed.
883
+ //
884
+ // The comment this replaces asserted the opposite order matched
885
+ // "the reference's _eval_function_call precedence exactly" -- but
886
+ // _eval_function_call / _BUILTIN_FN_NAMES are the old PYTHON
887
+ // evaluator, not OpenSCAD, and that prototype had it backwards.
888
+ // The C++ port inherited the mistake. Modules were always right
889
+ // (a user `module sphere()` does override the builtin); only
890
+ // functions resolved the wrong way round.
891
+ //
892
+ // bytecode_compiler.cpp's Op::CallFn site resolution has the same
893
+ // ordering and is fixed with it -- the VM is the default engine,
894
+ // so fixing only this one would leave the bug in place.
895
+ const oscad::ASTNode* decl = ctx.scope->lookupFunction(leftId->name);
896
+ if (decl && decl->kind() == oscad::NodeKind::FunctionDeclaration) {
897
+ // Call-site stop, in the CALLER's context, before descending
898
+ // into the callee (whose own body-entry stop comes later, from
899
+ // evalUserFunctionCore). Builtins deliberately get none --
900
+ // only the user-function and function-literal branches stop.
901
+ checkDebug(node, ctx, /*forced=*/false, /*exprLevel=*/false, /*callSite=*/true);
902
+ return evalUserFunction(leftId->name, static_cast<const oscad::FunctionDeclaration&>(*decl), node.arguments,
903
+ ctx, &node);
904
+ }
884
905
  if (isBuiltinFunctionName(leftId->name)) {
885
906
  // is_undef(name) is a probe: asking whether a name is defined
886
907
  // must not itself warn that it isn't. The $-branch of
@@ -901,17 +922,6 @@ Value Evaluator::evalFunctionCall(const oscad::PrimaryCall& node, EvalContext& c
901
922
  CallArgs args = resolveArgs(*this, node.arguments, ctx);
902
923
  return evalBuiltinFunction(*this, leftId->name, args, node);
903
924
  }
904
- const oscad::ASTNode* decl = ctx.scope->lookupFunction(leftId->name);
905
- if (decl && decl->kind() == oscad::NodeKind::FunctionDeclaration) {
906
- // Call-site stop, in the CALLER's context, before descending
907
- // into the callee (whose own body-entry stop comes later, from
908
- // evalUserFunctionCore). Builtins deliberately get none --
909
- // mirrors _eval_function_call, where only the user-function and
910
- // function-literal branches call _check_debug.
911
- checkDebug(node, ctx, /*forced=*/false, /*exprLevel=*/false, /*callSite=*/true);
912
- return evalUserFunction(leftId->name, static_cast<const oscad::FunctionDeclaration&>(*decl), node.arguments,
913
- ctx, &node);
914
- }
915
925
  }
916
926
 
917
927
  // Not a builtin or user function -- check whether the callee expression
@@ -121,3 +121,88 @@ TEST(ToRenderableBodies, A3dBodyPassesThroughUnchanged) {
121
121
  EXPECT_FALSE(renderable[0].flatPreview);
122
122
  EXPECT_NEAR(renderable[0].body->Volume(), 8.0, 1e-9);
123
123
  }
124
+
125
+ // -- AMF -------------------------------------------------------------------
126
+
127
+ namespace {
128
+
129
+ std::string readFile(const std::string& path) {
130
+ std::ifstream in(path);
131
+ return std::string((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
132
+ }
133
+
134
+ size_t countOf(const std::string& hay, const std::string& needle) {
135
+ size_t n = 0, at = 0;
136
+ while ((at = hay.find(needle, at)) != std::string::npos) { ++n; at += needle.size(); }
137
+ return n;
138
+ }
139
+
140
+ } // namespace
141
+
142
+ TEST(ExportAmf, IsAnOfferedFormat) {
143
+ const std::vector<std::string>& exts = exportExtensions();
144
+ EXPECT_NE(std::find(exts.begin(), exts.end(), ".amf"), exts.end());
145
+ }
146
+
147
+ TEST(ExportAmf, WritesOneObjectAndOneMaterialPerColour) {
148
+ // Two separated cubes, different colours: two objects, two materials.
149
+ std::vector<ColoredBody> bodies =
150
+ evalToBodies("color(\"red\") cube(2); translate([10,0,0]) color(\"blue\") cube(2);");
151
+ const std::string path = tempPath("two.amf").string();
152
+ exportModel(path, bodies, ExportOptions{});
153
+
154
+ const std::string xml = readFile(path);
155
+ EXPECT_EQ(countOf(xml, "<object id="), 2u);
156
+ EXPECT_EQ(countOf(xml, "<material id="), 2u);
157
+ // 12 triangles per cube, and every one lands in some volume.
158
+ EXPECT_EQ(countOf(xml, "<triangle>"), 24u);
159
+ EXPECT_NE(xml.find("<amf unit=\"millimeter\""), std::string::npos);
160
+ std::remove(path.c_str());
161
+ }
162
+
163
+ TEST(ExportAmf, TwoObjectsSharingAColourShareOneMaterial) {
164
+ std::vector<ColoredBody> bodies =
165
+ evalToBodies("color(\"red\") cube(2); translate([10,0,0]) color(\"red\") cube(2);");
166
+ const std::string path = tempPath("shared.amf").string();
167
+ exportModel(path, bodies, ExportOptions{});
168
+
169
+ const std::string xml = readFile(path);
170
+ EXPECT_EQ(countOf(xml, "<object id="), 2u);
171
+ EXPECT_EQ(countOf(xml, "<material id="), 1u);
172
+ std::remove(path.c_str());
173
+ }
174
+
175
+ TEST(ExportAmf, MaterialAndObjectIdsStartAtOne) {
176
+ // id 0 is reserved by the spec; a reader may drop anything using it.
177
+ std::vector<ColoredBody> bodies = evalToBodies("cube(2);");
178
+ const std::string path = tempPath("ids.amf").string();
179
+ exportModel(path, bodies, ExportOptions{});
180
+
181
+ const std::string xml = readFile(path);
182
+ EXPECT_NE(xml.find("<material id=\"1\">"), std::string::npos);
183
+ EXPECT_NE(xml.find("<object id=\"1\">"), std::string::npos);
184
+ EXPECT_EQ(xml.find("id=\"0\""), std::string::npos);
185
+ std::remove(path.c_str());
186
+ }
187
+
188
+ TEST(ExportAmf, NoGeometryThrows) {
189
+ std::vector<ColoredBody> empty;
190
+ EXPECT_THROW(exportModel(tempPath("empty.amf").string(), empty, ExportOptions{}), std::runtime_error);
191
+ }
192
+
193
+ TEST(ExportAmf, AMultiColouredObjectBecomesOneVolumePerColour) {
194
+ // A union welds these into ONE solid whose surface carries two colours.
195
+ // AMF has no per-face colour that slicers read reliably, so the object
196
+ // is written as one <volume> per colour -- which is exactly what a
197
+ // multimaterial slicer assigns to separate tools.
198
+ std::vector<ColoredBody> bodies = evalToBodies(
199
+ "union() { color(\"red\") cube(20); color(\"blue\") translate([10,5,5]) cube(20); }");
200
+ const std::string path = tempPath("multi.amf").string();
201
+ exportModel(path, bodies, ExportOptions{});
202
+
203
+ const std::string xml = readFile(path);
204
+ EXPECT_EQ(countOf(xml, "<object id="), 1u);
205
+ EXPECT_EQ(countOf(xml, "<volume materialid="), 2u);
206
+ EXPECT_EQ(countOf(xml, "<material id="), 2u);
207
+ std::remove(path.c_str());
208
+ }
@@ -508,18 +508,99 @@ TEST(ObjectBuiltin, PositionalNonObjectNonListArgumentIsUndef) {
508
508
  EXPECT_TRUE(isUndef(evalSrc("object(5)", ev)));
509
509
  }
510
510
 
511
- // -- Precedence: a builtin function name always wins over a same-named --
512
- // user function, unlike modules (where a user module CAN shadow a builtin).
511
+ // -- Precedence: a user function SHADOWS a same-named builtin ------------
512
+ //
513
+ // Same rule as modules, which always got this right. It used to be
514
+ // backwards for functions: the builtin won, because the port copied the old
515
+ // Python evaluator's _eval_function_call order rather than OpenSCAD's.
516
+ //
517
+ // Read off OpenSCAD 2026.02.01:
518
+ // function abs(x) = 999; echo(abs(-3)); -> 999
519
+ // function is_undef(x) = "mine"; echo(is_undef(q)); -> "mine", and it
520
+ // WARNS about the unknown variable q, because a user function
521
+ // evaluates its arguments normally -- only the builtin probe is
522
+ // silent.
523
+ //
524
+ // Every case runs under both engines: the VM decides builtin-vs-user at
525
+ // COMPILE time (bytecode_compiler.cpp), so it needs its own fix and its
526
+ // own coverage.
513
527
 
514
- TEST(FunctionBuiltinPrecedence, BuiltinWinsOverSameNamedUserFunction) {
515
- Evaluator ev;
516
- auto ast = parseSrc("function abs(x) = 999;\nresult = abs(-3);");
517
- auto scope = oscad::buildScopes(ast);
518
- auto* assign = dynamic_cast<oscad::Assignment*>(ast[1].get());
519
- ASSERT_NE(assign, nullptr);
520
- EvalContext ctx = EvalContext::makeRoot(scope.get());
521
- Value v = ev.evalExpr(*assign->expr, ctx);
522
- EXPECT_DOUBLE_EQ(asNum(v), 3.0); // builtin abs(), not the user's abs()
528
+ namespace {
529
+
530
+ class ScopedVm {
531
+ public:
532
+ explicit ScopedVm(bool enabled) { Evaluator::setBytecodeVmEnabledForTesting(enabled); }
533
+ ~ScopedVm() { Evaluator::setBytecodeVmEnabledForTesting(std::nullopt); }
534
+ };
535
+
536
+ std::vector<std::string> echoesOf(const std::string& src) {
537
+ std::vector<std::string> out;
538
+ oscadeval::test::evalSrc(src, [&](const std::string& m) { out.push_back(m); });
539
+ return out;
540
+ }
541
+
542
+ } // namespace
543
+
544
+ TEST(FunctionBuiltinPrecedence, UserFunctionShadowsSameNamedBuiltin) {
545
+ for (bool vm : {false, true}) {
546
+ ScopedVm guard(vm);
547
+ const std::vector<std::string> e = echoesOf("function abs(x) = 999;\necho(abs(-3));");
548
+ ASSERT_EQ(e.size(), 1u) << "vm=" << vm;
549
+ EXPECT_NE(e[0].find("999"), std::string::npos) << "vm=" << vm << ", got " << e[0];
550
+ }
551
+ }
552
+
553
+ TEST(FunctionBuiltinPrecedence, TheBuiltinStillWinsWhenNobodyRedefinesIt) {
554
+ for (bool vm : {false, true}) {
555
+ ScopedVm guard(vm);
556
+ const std::vector<std::string> e = echoesOf("echo(abs(-3));");
557
+ ASSERT_EQ(e.size(), 1u) << "vm=" << vm;
558
+ EXPECT_NE(e[0].find("3"), std::string::npos) << "vm=" << vm << ", got " << e[0];
559
+ EXPECT_EQ(e[0].find("999"), std::string::npos) << "vm=" << vm;
560
+ }
561
+ }
562
+
563
+ TEST(FunctionBuiltinPrecedence, ShadowingHoldsInsideAUserFunctionBody) {
564
+ // The call is inside another function, so on the VM path it is reached
565
+ // through a COMPILED chunk rather than the interpreter -- this is the
566
+ // case a compiler-side fix is needed for.
567
+ for (bool vm : {false, true}) {
568
+ ScopedVm guard(vm);
569
+ const std::vector<std::string> e =
570
+ echoesOf("function abs(x) = 999;\nfunction outer() = abs(-3);\necho(outer());");
571
+ ASSERT_EQ(e.size(), 1u) << "vm=" << vm;
572
+ EXPECT_NE(e[0].find("999"), std::string::npos) << "vm=" << vm << ", got " << e[0];
573
+ }
574
+ }
575
+
576
+ TEST(FunctionBuiltinPrecedence, AUserDefinedIsUndefShadowsTheProbe) {
577
+ // is_undef() is the one builtin with argument handling of its own (it
578
+ // must not warn about the name it is probing). A user's own is_undef
579
+ // takes over completely, arguments evaluated the ordinary way -- so the
580
+ // unknown-variable warning the probe suppresses DOES appear, exactly as
581
+ // upstream.
582
+ for (bool vm : {false, true}) {
583
+ ScopedVm guard(vm);
584
+ std::vector<std::string> msgs;
585
+ oscadeval::test::evalSrc("function is_undef(x) = \"mine\";\necho(is_undef(nothing));",
586
+ [&](const std::string& m) { msgs.push_back(m); });
587
+ bool sawMine = false, sawWarning = false;
588
+ for (const std::string& m : msgs) {
589
+ if (m.find("mine") != std::string::npos) sawMine = true;
590
+ if (m.find("nothing") != std::string::npos) sawWarning = true;
591
+ }
592
+ EXPECT_TRUE(sawMine) << "vm=" << vm;
593
+ EXPECT_TRUE(sawWarning) << "vm=" << vm << ": a user function evaluates its argument normally";
594
+ }
595
+ }
596
+
597
+ TEST(FunctionBuiltinPrecedence, TheIsUndefProbeIsStillSilentWhenNotRedefined) {
598
+ for (bool vm : {false, true}) {
599
+ ScopedVm guard(vm);
600
+ const std::vector<std::string> e = echoesOf("echo(is_undef(nothing));");
601
+ ASSERT_EQ(e.size(), 1u) << "vm=" << vm << ": probing must not warn";
602
+ EXPECT_NE(e[0].find("true"), std::string::npos) << "vm=" << vm << ", got " << e[0];
603
+ }
523
604
  }
524
605
 
525
606
  // -- Builtin argument diagnostics (reference parity) ---------------------