openscad-cpp-evaluator 1.50.2__tar.gz → 1.51.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 (241) hide show
  1. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/CLAUDE.md +25 -0
  2. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/colored_body.hpp +5 -0
  4. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/evaluator.hpp +6 -0
  5. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/export.hpp +4 -0
  6. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/pyproject.toml +1 -1
  7. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/CMakeLists.txt +1 -0
  8. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/builtins.hpp +5 -0
  9. openscad_cpp_evaluator-1.51.0/src/builtins/export_as.cpp +87 -0
  10. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/function_builtins.cpp +1 -0
  11. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/registry.cpp +3 -0
  12. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/colored_body.cpp +1 -0
  13. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/csg_resolve.cpp +1 -0
  14. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/export.cpp +63 -8
  15. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/CMakeLists.txt +1 -0
  16. openscad_cpp_evaluator-1.51.0/tests/test_export_as.cpp +108 -0
  17. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/.github/workflows/ci.yml +0 -0
  18. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/.github/workflows/wheels.yml +0 -0
  19. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/.gitignore +0 -0
  20. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/.gitmodules +0 -0
  21. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/LICENSE +0 -0
  23. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/README.md +0 -0
  24. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/bindings/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/bindings/ast_to_py.cpp +0 -0
  26. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/bindings/module.cpp +0 -0
  27. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/cmake/embed_font.cmake +0 -0
  28. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/cmake/patch_manifold.cmake +0 -0
  29. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/examples/CMakeLists.txt +0 -0
  30. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/examples/eval_perf_benchmark.cpp +0 -0
  31. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/examples/manifold_cache_reuse.cpp +0 -0
  32. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/examples/minimal_debugger.cpp +0 -0
  33. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  34. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/.gitignore +0 -0
  35. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  36. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  37. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/LICENSE +0 -0
  38. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/README.md +0 -0
  39. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  40. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  41. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  43. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  44. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  45. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  46. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  47. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  48. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  49. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  50. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  51. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  52. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  53. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  54. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  55. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  56. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  57. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  58. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  59. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  60. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  61. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  62. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  63. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  64. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  65. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  66. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  67. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  68. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  69. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  70. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  71. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  72. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  73. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  74. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  75. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  76. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  77. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  78. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  79. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  80. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  81. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  82. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  83. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  84. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  85. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  86. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  87. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  88. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  89. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  90. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  91. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  92. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  93. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  94. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  95. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_profile_time.cpp +0 -0
  96. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  97. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  98. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  99. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  100. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  101. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  102. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  103. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  104. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  105. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  106. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  107. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  108. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  109. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  110. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  111. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  112. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  113. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  114. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  115. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  116. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  117. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  118. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  119. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  120. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  121. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  122. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  123. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  124. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  125. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  126. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  127. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  128. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  129. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  130. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  131. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  132. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/reassign.hpp +0 -0
  133. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  134. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  135. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  136. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  137. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  138. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  139. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  140. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  141. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/resources/fonts/LICENSE +0 -0
  142. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  143. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  144. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  145. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  146. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/alpha_wrap.cpp +0 -0
  147. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/booleans.cpp +0 -0
  148. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/call_args.cpp +0 -0
  149. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/color.cpp +0 -0
  150. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/control.cpp +0 -0
  151. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/dxf_dim.cpp +0 -0
  152. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/extrude.cpp +0 -0
  153. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/import.cpp +0 -0
  154. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/modifiers.cpp +0 -0
  155. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/primitives_2d.cpp +0 -0
  156. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/primitives_3d.cpp +0 -0
  157. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/profile_time.cpp +0 -0
  158. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/roof.cpp +0 -0
  159. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/segments.cpp +0 -0
  160. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/surface.cpp +0 -0
  161. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/text.cpp +0 -0
  162. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/topology.cpp +0 -0
  163. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/builtins/transforms.cpp +0 -0
  164. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/bytecode_compiler.cpp +0 -0
  165. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/bytecode_vm.cpp +0 -0
  166. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/coverage.cpp +0 -0
  167. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/csg_generate.cpp +0 -0
  168. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/css_colors.cpp +0 -0
  169. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/debug/debug_repl.cpp +0 -0
  170. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/debug_profile.cpp +0 -0
  171. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/eval_context.cpp +0 -0
  172. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/eval_error.cpp +0 -0
  173. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/eval_use.cpp +0 -0
  174. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/expr_eval.cpp +0 -0
  175. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/format_closure.cpp +0 -0
  176. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/import/dxf_import.cpp +0 -0
  177. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/import/mesh_import.cpp +0 -0
  178. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/import/scene_import.cpp +0 -0
  179. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/import/surface_load.cpp +0 -0
  180. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/import/svg_import.cpp +0 -0
  181. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/manifold_cache.cpp +0 -0
  182. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/measure_geometry.cpp +0 -0
  183. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/mesh_check.cpp +0 -0
  184. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/stmt_eval.cpp +0 -0
  185. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/text/font_match.cpp +0 -0
  186. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/text/freetype_font_provider.cpp +0 -0
  187. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/text/text_metrics.cpp +0 -0
  188. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/user_calls.cpp +0 -0
  189. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/utf8.cpp +0 -0
  190. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/value.cpp +0 -0
  191. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/xkcd_colors.cpp +0 -0
  192. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/xkcd_colors.hpp +0 -0
  193. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/src/zip_stored.cpp +0 -0
  194. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_alpha_wrap.cpp +0 -0
  195. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_booleans.cpp +0 -0
  196. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_bytecode_compiler.cpp +0 -0
  197. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_children_separate.cpp +0 -0
  198. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_cli.cpp +0 -0
  199. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_control_flow.cpp +0 -0
  200. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_coverage.cpp +0 -0
  201. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_csg_tree.cpp +0 -0
  202. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_debug_hooks.cpp +0 -0
  203. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_discretizer.cpp +0 -0
  204. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_dxf_svg_import.cpp +0 -0
  205. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_eval_context.cpp +0 -0
  206. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_export.cpp +0 -0
  207. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_expr_eval.cpp +0 -0
  208. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_extrude_roof.cpp +0 -0
  209. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_feature_detection.cpp +0 -0
  210. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_function_builtins.cpp +0 -0
  211. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_helpers.hpp +0 -0
  212. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_import_export.cpp +0 -0
  213. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_include_cache.cpp +0 -0
  214. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_manifold_cache.cpp +0 -0
  215. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_mesh_check.cpp +0 -0
  216. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_multi_color_merge.cpp +0 -0
  217. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_openscad_parity.cpp +0 -0
  218. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_primitives.cpp +0 -0
  219. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_profile_time.cpp +0 -0
  220. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_profiling.cpp +0 -0
  221. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_python_bindings.py +0 -0
  222. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_render_expr.cpp +0 -0
  223. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_scope_trail.cpp +0 -0
  224. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_scoping.cpp +0 -0
  225. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_surface.cpp +0 -0
  226. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_tail_calls.cpp +0 -0
  227. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_text.cpp +0 -0
  228. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_transforms.cpp +0 -0
  229. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_undef_args.cpp +0 -0
  230. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_unexpected_args.cpp +0 -0
  231. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_use.cpp +0 -0
  232. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_value.cpp +0 -0
  233. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_viewport_params.cpp +0 -0
  234. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tests/test_zip_stored.cpp +0 -0
  235. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/third_party/predicates/predicates.c +0 -0
  236. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/third_party/predicates/predicates.h +0 -0
  237. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tools/cli/CMakeLists.txt +0 -0
  238. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tools/cli/cli_lib.cpp +0 -0
  239. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tools/cli/cli_lib.hpp +0 -0
  240. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/tools/cli/main.cpp +0 -0
  241. {openscad_cpp_evaluator-1.50.2 → openscad_cpp_evaluator-1.51.0}/uv.lock +0 -0
@@ -2012,3 +2012,28 @@ warning). A non-positive or non-numeric `alpha`/`offset` warns and leaves the ch
2012
2012
  faces). Time follows OUTPUT size. Not done: CGAL's manifold post-pass for very thin features
2013
2013
  (no failure seen yet), and the same input scaled can differ by a near-tie or two (same shape,
2014
2014
  volume within 0.2%), which is why `DefaultsScaleWithTheModel` compares within tolerances.
2015
+
2016
+ ## `export_as()`
2017
+
2018
+ `export_as("name") { ... }` names its children's geometry as one object in a file that holds several
2019
+ (BelfrySCAD #918; `supported_feature("export-as")` is 1). In 3MF it is the `<object name="...">`
2020
+ attribute, in OBJ the `o` line (whitespace becomes `_`), in AMF `<metadata type="name">`; the other
2021
+ formats have no object names and ignore it. Geometry outside any `export_as()` is named as before.
2022
+
2023
+ - **The name rides on the body** (`ColoredBody::exportName`), so everything that copies its
2024
+ children's bodies carries it: transforms, `color()`, the modifiers, `render()`, `profile_time()`,
2025
+ user modules. An op that **builds** a new body (any boolean, hull, extrude, ...) drops it, and
2026
+ `checkExportNames` warns about an `export_as()` inside one, read off the resolved tree so a
2027
+ ManifoldCache hit warns the same. An inner `export_as()` keeps its name; the outer one names only
2028
+ what is still unnamed.
2029
+ - **Export** (`splitBodiesForExport`): the name is part of the object key, so a named part is its own
2030
+ object whatever the colour options say, and two touching same-coloured parts no longer weld. With
2031
+ colours split, colour still keys inside a name. A named part is **always** split into its connected
2032
+ pieces, whatever `splitComponents` says, and a name that ends up as several objects (pieces,
2033
+ colours, an open shell beside a solid) is numbered `name_1`, `name_2`, ... so every object stays
2034
+ told apart. The same name on two `export_as()` calls warns and merges them into one part, so
2035
+ touching uses weld and disjoint ones are numbered pieces.
2036
+ - A missing or empty name warns and leaves the children unnamed. Not upstream: OpenSCAD has no
2037
+ `export_as()` and drops an unknown module's children, so a portable script guards on
2038
+ `supported_feature("export-as")`.
2039
+ - `src/builtins/export_as.cpp`; tests in `tests/test_export_as.cpp`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.50.2
3
+ Version: 1.51.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT AND OFL-1.1
@@ -8,6 +8,7 @@
8
8
  #include <array>
9
9
  #include <memory>
10
10
  #include <optional>
11
+ #include <string>
11
12
  #include <vector>
12
13
 
13
14
  namespace oscadeval {
@@ -92,6 +93,10 @@ struct ColoredBody {
92
93
  manifold::mat3x4 sectionXform{manifold::vec3(1, 0, 0), manifold::vec3(0, 1, 0),
93
94
  manifold::vec3(0, 0, 1), manifold::vec3(0, 0, 0)};
94
95
  BodyRole role = BodyRole::Normal;
96
+ // The name export_as() gave this body: the object it becomes in a file
97
+ // that holds several (splitBodiesForExport). Copied along by everything
98
+ // that copies the body; an op that builds a new body drops it.
99
+ std::optional<std::string> exportName;
95
100
  std::optional<std::vector<std::array<float, 4>>> triColors; // per-triangle RGBA, multi-color CSG merges only
96
101
 
97
102
  // The operands a union() merged into this body, kept unmerged, each as
@@ -620,6 +620,12 @@ public:
620
620
  // definition for why this cannot be done by tagging bodies.
621
621
  void rerootAtShowOnly(std::vector<std::unique_ptr<CSGNode>>& tree);
622
622
 
623
+ // Warns about an export_as() whose name cannot reach the file: one
624
+ // inside an operation that merges it with its siblings, or a name used
625
+ // by more than one export_as(). Read off the resolved tree, so a
626
+ // ManifoldCache hit says the same as a fresh build. See export_as.cpp.
627
+ void checkExportNames(const std::vector<std::unique_ptr<CSGNode>>& tree);
628
+
623
629
  // parent_module(idx) support: walks the live module-call stack
624
630
  // (innermost last) and returns the name `idx` levels up from the
625
631
  // current module, or undef if out of range. Public because
@@ -22,6 +22,10 @@ struct ExportObject {
22
22
  std::vector<uint32_t> tris; // vertex-index triples
23
23
  std::array<float, 4> color = kDefaultExportColor;
24
24
  std::vector<std::array<float, 4>> triColors;
25
+ // export_as()'s name, numbered "name_1", "name_2", ... when the part
26
+ // comes out as several objects. Empty for geometry no export_as()
27
+ // named; each writer then names it as it always has.
28
+ std::string name;
25
29
  };
26
30
 
27
31
  // The implicit top-level union, cut into objects that never overlap.
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.50.2"
7
+ version = "1.51.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -48,6 +48,7 @@ add_library(openscad_cpp_evaluator STATIC
48
48
  builtins/import.cpp
49
49
  builtins/topology.cpp
50
50
  builtins/alpha_wrap.cpp
51
+ builtins/export_as.cpp
51
52
  ../third_party/predicates/predicates.c
52
53
  builtins/extrude.cpp
53
54
  builtins/roof.cpp
@@ -203,6 +203,11 @@ CSGParams resolveAlphaWrap(Evaluator& ev, const oscad::ModularCall& node, EvalCo
203
203
  std::vector<ColoredBody> generateAlphaWrap(Evaluator& ev, const CSGParams& params,
204
204
  const std::vector<std::unique_ptr<CSGNode>>& children,
205
205
  const oscad::ASTNode& node);
206
+ // export_as(name) -- names its children's object in an export (export_as.cpp).
207
+ CSGParams resolveExportAs(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
208
+ std::vector<ColoredBody> generateExportAs(Evaluator& ev, const CSGParams& params,
209
+ const std::vector<std::unique_ptr<CSGNode>>& children,
210
+ const oscad::ASTNode& node);
206
211
  CSGParams resolveSimplify(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
207
212
  std::vector<ColoredBody> generateSimplify(Evaluator& ev, const CSGParams& params,
208
213
  const std::vector<std::unique_ptr<CSGNode>>& children,
@@ -0,0 +1,87 @@
1
+ #include "builtins.hpp"
2
+
3
+ #include "openscad_cpp_evaluator/call_args.hpp"
4
+ #include "openscad_cpp_evaluator/evaluator.hpp"
5
+
6
+ #include <map>
7
+ #include <set>
8
+
9
+ namespace oscadeval {
10
+
11
+ // export_as("name") { ... } -- names the geometry of its children as one
12
+ // object in a file that holds several (3MF, OBJ, AMF); see
13
+ // splitBodiesForExport. The name rides on ColoredBody::exportName, which
14
+ // every transform, color() and modifier carries along because they copy the
15
+ // body. An inner export_as() keeps its own name: only unnamed bodies take
16
+ // this one.
17
+ CSGParams resolveExportAs(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
18
+ auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
19
+ CSGParams params;
20
+ const Value name = getArg(args, 0, "name");
21
+ const std::string* s = std::get_if<std::string>(&name);
22
+ if (s && !s->empty()) params["name"] = *s;
23
+ else ev.warn("export_as() needs a non-empty name string; its children are exported unnamed", &node.position());
24
+ EvalContext blockCtx = ev.blockScope(effCtx);
25
+ ev.evalChildren(node.children, blockCtx);
26
+ return params;
27
+ }
28
+
29
+ std::vector<ColoredBody> generateExportAs(Evaluator&, const CSGParams& params,
30
+ const std::vector<std::unique_ptr<CSGNode>>& children,
31
+ const oscad::ASTNode&) {
32
+ std::vector<ColoredBody> result = flattenCsgTree(children);
33
+ auto it = params.find("name");
34
+ if (it == params.end()) return result;
35
+ const std::string& name = std::get<std::string>(it->second);
36
+ for (ColoredBody& b : result) {
37
+ if (!b.exportName) b.exportName = name;
38
+ }
39
+ return result;
40
+ }
41
+
42
+ namespace {
43
+
44
+ // The builtins whose result is their children's own bodies, copied (so an
45
+ // exportName survives them). Every other builtin builds new bodies from its
46
+ // children and drops the name.
47
+ bool keepsChildBodies(const CSGNode& n) {
48
+ static const std::set<std::string, std::less<>> kinds = {
49
+ "translate", "rotate", "scale", "mirror", "multmatrix", "resize", "color", "highlight",
50
+ "background", "show_only", "render", "profile_time", "export_as", "children",
51
+ };
52
+ return !n.isBuiltin || kinds.count(n.kind) > 0;
53
+ }
54
+
55
+ void walkExportAs(Evaluator& ev, const std::vector<std::unique_ptr<CSGNode>>& nodes, const CSGNode* merger,
56
+ std::map<std::string, int>& uses) {
57
+ for (const std::unique_ptr<CSGNode>& c : nodes) {
58
+ const CSGNode* childMerger = merger;
59
+ if (c->isBuiltin && c->kind == "export_as") {
60
+ auto it = c->params.find("name");
61
+ if (it != c->params.end() && c->node) {
62
+ const std::string& name = std::get<std::string>(it->second);
63
+ if (merger) {
64
+ ev.warn("export_as(\"" + name + "\") is inside " + merger->kind + "(), which merges it with " +
65
+ "its siblings, so the name is lost",
66
+ &c->node->position());
67
+ } else if (++uses[name] == 2) {
68
+ ev.warn("export_as(\"" + name + "\") is used more than once; those parts are merged into one " +
69
+ "part named \"" + name + "\"",
70
+ &c->node->position());
71
+ }
72
+ }
73
+ } else if (!merger && !keepsChildBodies(*c)) {
74
+ childMerger = c.get();
75
+ }
76
+ walkExportAs(ev, c->children, childMerger, uses);
77
+ }
78
+ }
79
+
80
+ } // namespace
81
+
82
+ void Evaluator::checkExportNames(const std::vector<std::unique_ptr<CSGNode>>& tree) {
83
+ std::map<std::string, int> uses;
84
+ walkExportAs(*this, tree, nullptr, uses);
85
+ }
86
+
87
+ } // namespace oscadeval
@@ -1002,6 +1002,7 @@ const std::unordered_map<std::string, double>& featureLevels() {
1002
1002
  {"discretization-by-error", 1.0}, // $fe, always on (upstream: behind --enable)
1003
1003
  {"profile-time", 1.0}, // profile_time(label) { ... } and profile_time(label) expr
1004
1004
  {"alpha-wrap", 1.0}, // alpha_wrap(alpha, offset)
1005
+ {"export-as", 1.0}, // export_as(name) { ... }
1005
1006
  };
1006
1007
  return levels;
1007
1008
  }
@@ -40,6 +40,7 @@ const std::unordered_map<std::string_view, ResolveFn>& resolveDispatch() {
40
40
  {"simplify", &resolveSimplify},
41
41
  {"mesh_repair", &resolveMeshRepair},
42
42
  {"alpha_wrap", &resolveAlphaWrap},
43
+ {"export_as", &resolveExportAs},
43
44
  {"levelset", &resolveLevelSet},
44
45
  {"offset", &resolveOffset},
45
46
  {"surface", &resolveSurface},
@@ -84,6 +85,7 @@ const std::unordered_map<std::string_view, GenerateFn>& generateDispatch() {
84
85
  {"simplify", &generateSimplify},
85
86
  {"mesh_repair", &generateMeshRepair},
86
87
  {"alpha_wrap", &generateAlphaWrap},
88
+ {"export_as", &generateExportAs},
87
89
  {"levelset", &generateLevelSet},
88
90
  {"offset", &generateOffset},
89
91
  {"surface", &generateSurface},
@@ -138,6 +140,7 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
138
140
  {"simplify", {"tolerance"}},
139
141
  {"mesh_repair", {"tolerance"}},
140
142
  {"alpha_wrap", {"alpha", "offset"}},
143
+ {"export_as", {"name"}},
141
144
  {"levelset", {"field", "bounds", "isovalue", "invert", "edge"}},
142
145
  {"offset", {"r", "delta", "chamfer"}},
143
146
  {"surface", {"file", "center", "convexity", "invert"}},
@@ -62,6 +62,7 @@ std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodi
62
62
  flat.section = cb.section;
63
63
  flat.sectionXform = cb.sectionXform;
64
64
  flat.role = cb.role;
65
+ flat.exportName = cb.exportName;
65
66
  out.push_back(std::move(flat));
66
67
  } else {
67
68
  out.push_back(cb);
@@ -293,6 +293,7 @@ std::vector<ColoredBody> Evaluator::evaluateImpl(const NodeList& nodes, EvalCont
293
293
  } generateRequestedGuard{generateRequested_};
294
294
  std::vector<std::unique_ptr<CSGNode>> tree = resolveTreeImpl(nodes, ctx);
295
295
  rerootAtShowOnly(tree);
296
+ checkExportNames(tree);
296
297
  const Clock::time_point resolveEnd = profiling_ ? Clock::now() : Clock::time_point{};
297
298
  // generate = false is the resolve-only path: the language has run and
298
299
  // the tree is built, so anything the script itself gets wrong has already
@@ -8,12 +8,14 @@
8
8
  #include <manifold/manifold.h>
9
9
 
10
10
  #include <algorithm>
11
+ #include <cctype>
11
12
  #include <cmath>
12
13
  #include <cstdint>
13
14
  #include <cstdio>
14
15
  #include <ctime>
15
16
  #include <fstream>
16
17
  #include <locale>
18
+ #include <map>
17
19
  #include <sstream>
18
20
  #include <optional>
19
21
  #include <stdexcept>
@@ -103,6 +105,21 @@ void writeStl(const std::string& path, const std::vector<ColoredBody>& bodies) {
103
105
  }
104
106
  }
105
107
 
108
+ // An export_as() name as XML attribute or element text.
109
+ static std::string xmlEscape(const std::string& s) {
110
+ std::string out;
111
+ for (char c : s) {
112
+ switch (c) {
113
+ case '&': out += "&amp;"; break;
114
+ case '<': out += "&lt;"; break;
115
+ case '>': out += "&gt;"; break;
116
+ case '"': out += "&quot;"; break;
117
+ default: out += c;
118
+ }
119
+ }
120
+ return out;
121
+ }
122
+
106
123
  void writeObj(const std::string& path, const std::vector<ExportObject>& objects) {
107
124
  // OBJ carries no colour of its own: `usemtl` names an entry in a .mtl
108
125
  // sitting next to the .obj, so an OBJ export writes TWO files. A reader
@@ -140,7 +157,16 @@ void writeObj(const std::string& path, const std::vector<ExportObject>& objects)
140
157
  size_t objIndex = 0;
141
158
  for (const ExportObject& o : objects) {
142
159
  ++objIndex;
143
- out << "o object_" << objIndex << "\n";
160
+ if (o.name.empty()) {
161
+ out << "o object_" << objIndex << "\n";
162
+ } else {
163
+ // An OBJ name runs to the end of the line, and readers split it
164
+ // at whitespace.
165
+ std::string name = o.name;
166
+ for (char& c : name)
167
+ if (std::isspace(static_cast<unsigned char>(c))) c = '_';
168
+ out << "o " << name << "\n";
169
+ }
144
170
  const size_t vertCount = o.verts.size() / 3;
145
171
  for (size_t v = 0; v < vertCount; ++v) {
146
172
  out << "v " << formatG6(o.verts[v * 3]) << " " << formatG6(o.verts[v * 3 + 1]) << " "
@@ -265,7 +291,9 @@ void writeThreeMf(const std::string& path, const std::vector<ExportObject>& obje
265
291
  resources += "</m:colorgroup>";
266
292
 
267
293
  const int objectId = nextId++;
268
- resources += "<object id=\"" + std::to_string(objectId) + "\" type=\"model\" pid=\"" +
294
+ resources += "<object id=\"" + std::to_string(objectId) + "\" type=\"model\"";
295
+ if (!o.name.empty()) resources += " name=\"" + xmlEscape(o.name) + "\"";
296
+ resources += " pid=\"" +
269
297
  std::to_string(colorGroupId) + "\" pindex=\"0\"><mesh><vertices>";
270
298
  const size_t vertCount = o.verts.size() / 3;
271
299
  for (size_t v = 0; v < vertCount; ++v) {
@@ -374,13 +402,21 @@ bool boxesOverlap(const manifold::Box& ba, const manifold::Box& bb) {
374
402
  // A colour key that keeps per-triangle-coloured bodies apart from
375
403
  // everything, including each other. Their colours index their own triangle
376
404
  // list, and a union would rewrite that list and lose them.
405
+ //
406
+ // An export_as() name keys too: a named part is its own object whatever
407
+ // the colour options say. With colours not split (`byColor` false), the
408
+ // name is all that keys.
377
409
  struct ColorKey {
378
410
  bool perTriangle = false;
379
411
  size_t index = 0; // position in `solids`, for the per-triangle case
380
412
  bool hasColor = false;
381
413
  std::array<float, 4> color{};
414
+ std::optional<std::string> name;
415
+ bool byColor = true;
382
416
 
383
417
  bool operator==(const ColorKey& o) const {
418
+ if (name != o.name) return false;
419
+ if (!byColor) return true;
384
420
  if (perTriangle != o.perTriangle) return false;
385
421
  if (perTriangle) return index == o.index;
386
422
  if (hasColor != o.hasColor) return false;
@@ -476,6 +512,7 @@ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& b
476
512
  std::optional<std::array<float, 4>> color;
477
513
  std::vector<std::array<float, 4>> triColors;
478
514
  std::vector<uint32_t> tris;
515
+ std::optional<std::string> name;
479
516
  };
480
517
 
481
518
  std::vector<Solid> solids;
@@ -503,6 +540,7 @@ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& b
503
540
  if (obj.tris.empty()) continue;
504
541
  obj.color = cb.color.value_or(kDefaultExportColor);
505
542
  if (cb.triColors) obj.triColors = resolveThemeColors(*cb.triColors);
543
+ obj.name = cb.exportName.value_or("");
506
544
  loose.push_back(std::move(obj));
507
545
  if (openParts) openParts->push_back(index);
508
546
  continue;
@@ -511,6 +549,7 @@ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& b
511
549
  Solid s;
512
550
  s.man = *cb.body;
513
551
  s.color = cb.color;
552
+ s.name = cb.exportName;
514
553
  if (cb.triColors) s.triColors = resolveThemeColors(*cb.triColors);
515
554
  s.tris = s.man.GetMeshGL().triVerts;
516
555
  if (s.tris.empty()) continue;
@@ -527,6 +566,8 @@ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& b
527
566
  k.index = i;
528
567
  k.hasColor = solids[i].color.has_value();
529
568
  if (k.hasColor) k.color = *solids[i].color;
569
+ k.name = solids[i].name;
570
+ k.byColor = splitColors;
530
571
  keys.push_back(k);
531
572
  }
532
573
  // Splitting by colour is for a MULTI-MATERIAL print, where each
@@ -536,7 +577,7 @@ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& b
536
577
  // separate walls. Unioning everything here (rather than
537
578
  // concatenating the per-colour objects afterwards) is what makes
538
579
  // touching regions weld instead of leaving coincident faces.
539
- const bool allSame = !splitColors ||
580
+ const bool allSame =
540
581
  std::all_of(keys.begin(), keys.end(), [&](const ColorKey& k) { return k == keys[0]; });
541
582
 
542
583
  if (allSame) {
@@ -597,7 +638,7 @@ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& b
597
638
  Claimed c;
598
639
  c.man = std::move(piece);
599
640
  c.color = s.color;
600
- c.triColors = s.triColors;
641
+ if (splitColors) c.triColors = s.triColors;
601
642
  c.sourceTris = s.tris;
602
643
  c.key = keys[n];
603
644
  owned.push_back(std::move(c));
@@ -623,10 +664,12 @@ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& b
623
664
  for (const Claimed& g : claimedGroups) {
624
665
  // Decompose() is the rule-3 split, and it is opt-in: without it this
625
666
  // colour group stays one object however many disjoint pieces it is
626
- // in, which is what OpenSCAD writes. A single-component solid comes
627
- // back as a one-element list either way, so there is no special case.
667
+ // in, which is what OpenSCAD writes. A named part always splits: its
668
+ // pieces are numbered below, so they stay one part by name. A
669
+ // single-component solid comes back as a one-element list either
670
+ // way, so there is no special case.
628
671
  std::vector<manifold::Manifold> parts;
629
- if (splitComponents) parts = g.man.Decompose();
672
+ if (splitComponents || g.key.name) parts = g.man.Decompose();
630
673
  if (parts.empty()) parts.push_back(g.man);
631
674
  for (const manifold::Manifold& part : parts) {
632
675
  if (part.IsEmpty()) continue;
@@ -635,10 +678,20 @@ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& b
635
678
  if (obj.tris.empty()) continue;
636
679
  obj.color = g.color.value_or(kDefaultExportColor);
637
680
  obj.triColors = carryTriColors(g.triColors, g.sourceTris, obj.tris);
681
+ obj.name = g.key.name.value_or("");
638
682
  out.push_back(std::move(obj));
639
683
  }
640
684
  }
641
685
  out.insert(out.end(), std::make_move_iterator(loose.begin()), std::make_move_iterator(loose.end()));
686
+
687
+ // A name that came out as several objects -- disconnected pieces,
688
+ // colours, an open shell beside a solid -- is numbered, so every object
689
+ // in the file is still told apart by name.
690
+ std::map<std::string, int> total, seen;
691
+ for (const ExportObject& o : out)
692
+ if (!o.name.empty()) ++total[o.name];
693
+ for (ExportObject& o : out)
694
+ if (!o.name.empty() && total[o.name] > 1) o.name += "_" + std::to_string(++seen[o.name]);
642
695
  return out;
643
696
  }
644
697
 
@@ -924,7 +977,9 @@ void writeAmf(const std::string& path, const std::vector<ExportObject>& objects)
924
977
  }
925
978
  for (size_t oi = 0; oi < objects.size(); ++oi) {
926
979
  const ExportObject& o = objects[oi];
927
- out << " <object id=\"" << (oi + 1) << "\">\n <mesh>\n <vertices>\n";
980
+ out << " <object id=\"" << (oi + 1) << "\">\n";
981
+ if (!o.name.empty()) out << " <metadata type=\"name\">" << xmlEscape(o.name) << "</metadata>\n";
982
+ out << " <mesh>\n <vertices>\n";
928
983
  for (size_t v = 0; v < o.verts.size() / 3; ++v) {
929
984
  out << " <vertex><coordinates><x>" << formatG6(o.verts[v * 3]) << "</x><y>"
930
985
  << formatG6(o.verts[v * 3 + 1]) << "</y><z>" << formatG6(o.verts[v * 3 + 2])
@@ -46,6 +46,7 @@ add_executable(oscad_eval_tests
46
46
  test_viewport_params.cpp
47
47
  test_unexpected_args.cpp
48
48
  test_alpha_wrap.cpp
49
+ test_export_as.cpp
49
50
  test_cli.cpp
50
51
  )
51
52
  # oscad_cli_lib PUBLIC-links openscad_cpp_evaluator, so naming it here too
@@ -0,0 +1,108 @@
1
+ #include "openscad_cpp_evaluator/export.hpp"
2
+
3
+ #include "test_helpers.hpp"
4
+
5
+ #include <filesystem>
6
+ #include <fstream>
7
+ #include <gtest/gtest.h>
8
+ #include <sstream>
9
+
10
+ using namespace oscadeval;
11
+ using namespace oscadeval::test;
12
+
13
+ namespace {
14
+
15
+ std::vector<std::string> namesOf(const std::vector<ExportObject>& objs) {
16
+ std::vector<std::string> out;
17
+ for (const ExportObject& o : objs) out.push_back(o.name);
18
+ return out;
19
+ }
20
+
21
+ std::vector<std::string> split(const std::string& code, bool components = false, bool colors = true) {
22
+ Evaluated e = evaluateSrc(code);
23
+ return namesOf(splitBodiesForExport(e.bodies, nullptr, components, colors));
24
+ }
25
+
26
+ std::vector<std::string> warningsOf(const std::string& code) {
27
+ std::vector<std::string> log;
28
+ evaluateSrc(code, [&](const std::string& m) { log.push_back(m); });
29
+ return log;
30
+ }
31
+
32
+ std::string slurp(const std::filesystem::path& p) {
33
+ std::ifstream in(p);
34
+ std::stringstream ss;
35
+ ss << in.rdbuf();
36
+ return ss.str();
37
+ }
38
+
39
+ } // namespace
40
+
41
+ TEST(ExportAs, TouchingNamedPartsStaySeparate) {
42
+ // Same colour, sharing a face: without names these weld into one object.
43
+ EXPECT_EQ(split(R"(export_as("jar") cube(10); export_as("lid") translate([10,0,0]) cube(10);)"),
44
+ (std::vector<std::string>{"jar", "lid"}));
45
+ }
46
+
47
+ TEST(ExportAs, NameSurvivesTransformsAndColor) {
48
+ EXPECT_EQ(split(R"(translate([5,0,0]) color("red") export_as("jar") rotate(10) cube(10);)"),
49
+ (std::vector<std::string>{"jar"}));
50
+ }
51
+
52
+ TEST(ExportAs, UnnamedGeometryKeepsAutomaticNaming) {
53
+ EXPECT_EQ(split(R"(export_as("jar") cube(10); translate([20,0,0]) cube(10);)"),
54
+ (std::vector<std::string>{"jar", ""}));
55
+ }
56
+
57
+ TEST(ExportAs, NamedPartIsOneObjectEvenWithColorsNotSplit) {
58
+ EXPECT_EQ(split(R"(export_as("jar") cube(10); export_as("lid") translate([10,0,0]) cube(10);)", false, false),
59
+ (std::vector<std::string>{"jar", "lid"}));
60
+ }
61
+
62
+ TEST(ExportAs, PiecesOfOnePartAreNumbered) {
63
+ const std::string code = R"(export_as("leg") { cube(1); translate([5,0,0]) cube(1); })";
64
+ // Whatever the components option says: a named part always splits.
65
+ EXPECT_EQ(split(code), (std::vector<std::string>{"leg_1", "leg_2"}));
66
+ EXPECT_EQ(split(code, /*components=*/true), (std::vector<std::string>{"leg_1", "leg_2"}));
67
+ EXPECT_EQ(split(R"(export_as("leg") cube(1);)"), (std::vector<std::string>{"leg"}));
68
+ }
69
+
70
+ TEST(ExportAs, InnerNameWins) {
71
+ EXPECT_EQ(split(R"(export_as("kit") { export_as("jar") cube(1); translate([5,0,0]) cube(1); })"),
72
+ (std::vector<std::string>{"jar", "kit"}));
73
+ }
74
+
75
+ TEST(ExportAs, NameInsideAMergingOperationWarns) {
76
+ const auto log = warningsOf(R"(union() { export_as("jar") cube(1); cube(2); })");
77
+ EXPECT_TRUE(anyContains(log, "export_as(\"jar\") is inside union()"));
78
+ // Through a user module too: the module is not the merger, union() is.
79
+ EXPECT_TRUE(anyContains(warningsOf(R"(module m() export_as("a") cube(1); difference() { m(); cube(0.5); })"),
80
+ "export_as(\"a\") is inside difference()"));
81
+ }
82
+
83
+ TEST(ExportAs, DuplicateNameWarnsAndMerges) {
84
+ const std::string code = R"(export_as("jar") cube(1); export_as("jar") translate([5,0,0]) cube(1);)";
85
+ EXPECT_TRUE(anyContains(warningsOf(code), "export_as(\"jar\") is used more than once"));
86
+ // One name, two disjoint pieces: numbered.
87
+ EXPECT_EQ(split(code), (std::vector<std::string>{"jar_1", "jar_2"}));
88
+ }
89
+
90
+ TEST(ExportAs, MissingNameWarns) {
91
+ EXPECT_TRUE(anyContains(warningsOf("export_as() cube(1);"), "export_as() needs a non-empty name string"));
92
+ EXPECT_EQ(split("export_as() cube(1);"), (std::vector<std::string>{""}));
93
+ }
94
+
95
+ TEST(ExportAs, WritersCarryTheName) {
96
+ Evaluated e = evaluateSrc(R"(export_as("big jar") cube(10); export_as("a&b") translate([20,0,0]) cube(1);)");
97
+ const auto dir = std::filesystem::temp_directory_path();
98
+ exportModel((dir / "oscad_eval_test_export_as.obj").string(), e.bodies);
99
+ const std::string obj = slurp(dir / "oscad_eval_test_export_as.obj");
100
+ EXPECT_NE(obj.find("o big_jar\n"), std::string::npos);
101
+ exportModel((dir / "oscad_eval_test_export_as.amf").string(), e.bodies);
102
+ const std::string amf = slurp(dir / "oscad_eval_test_export_as.amf");
103
+ EXPECT_NE(amf.find("<metadata type=\"name\">a&amp;b</metadata>"), std::string::npos);
104
+ }
105
+
106
+ TEST(ExportAs, FeatureIsAdvertised) {
107
+ EXPECT_TRUE(anyContains(warningsOf(R"(echo(supported_feature("export-as"));)"), "ECHO: 1"));
108
+ }