openscad-cpp-evaluator 1.45.1__tar.gz → 1.45.3__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 (235) hide show
  1. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/CLAUDE.md +31 -5
  2. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/PKG-INFO +1 -1
  3. openscad_cpp_evaluator-1.45.3/include/openscad_cpp_evaluator/mesh_import.hpp +49 -0
  4. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/pyproject.toml +1 -1
  5. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/import/mesh_import.cpp +49 -20
  6. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/import/scene_import.cpp +66 -15
  7. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_import_export.cpp +286 -2
  8. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/uv.lock +1 -1
  9. openscad_cpp_evaluator-1.45.1/include/openscad_cpp_evaluator/mesh_import.hpp +0 -36
  10. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/.github/workflows/ci.yml +0 -0
  11. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/.github/workflows/wheels.yml +0 -0
  12. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/.gitignore +0 -0
  13. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/.gitmodules +0 -0
  14. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/CMakeLists.txt +0 -0
  15. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/LICENSE +0 -0
  16. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/README.md +0 -0
  17. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/bindings/CMakeLists.txt +0 -0
  18. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/bindings/ast_to_py.cpp +0 -0
  19. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/bindings/module.cpp +0 -0
  20. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/cmake/embed_font.cmake +0 -0
  21. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/cmake/patch_manifold.cmake +0 -0
  22. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/examples/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/examples/eval_perf_benchmark.cpp +0 -0
  24. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/examples/manifold_cache_reuse.cpp +0 -0
  25. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/examples/minimal_debugger.cpp +0 -0
  26. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  27. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/.gitignore +0 -0
  28. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  29. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  30. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/LICENSE +0 -0
  31. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/README.md +0 -0
  32. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  33. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  34. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  35. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  36. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  37. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  38. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  39. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  40. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  41. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  43. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  44. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  45. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  46. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  47. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  48. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  49. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  50. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/api.cpp +0 -0
  51. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  52. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  53. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  54. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  55. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  56. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  57. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  58. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  59. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  60. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  61. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  62. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  63. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  64. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  65. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  66. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  67. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/position.cpp +0 -0
  68. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  69. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  70. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  71. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  72. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  73. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  74. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  75. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  76. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  77. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  78. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  79. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  80. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  81. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  82. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  83. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  84. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  85. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  86. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  87. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  88. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_profile_time.cpp +0 -0
  89. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  90. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  91. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  92. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  93. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  94. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  95. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  96. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  97. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  98. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  99. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  100. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  101. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  102. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  103. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  104. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  105. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  106. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  107. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  108. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  109. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  110. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  111. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  112. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  113. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  114. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  115. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  116. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/export.hpp +0 -0
  117. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  118. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  119. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  120. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  121. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  122. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  123. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  124. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  125. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  126. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  127. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  128. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  129. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  130. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  131. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  132. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/value.hpp +0 -0
  133. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  134. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/python/openscad_cpp_evaluator/__init__.py +0 -0
  135. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/resources/fonts/LICENSE +0 -0
  136. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  137. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  138. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  139. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  140. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/CMakeLists.txt +0 -0
  141. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/booleans.cpp +0 -0
  142. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/builtins.hpp +0 -0
  143. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/call_args.cpp +0 -0
  144. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/color.cpp +0 -0
  145. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/control.cpp +0 -0
  146. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/dxf_dim.cpp +0 -0
  147. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/extrude.cpp +0 -0
  148. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/function_builtins.cpp +0 -0
  149. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/import.cpp +0 -0
  150. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/modifiers.cpp +0 -0
  151. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/primitives_2d.cpp +0 -0
  152. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/primitives_3d.cpp +0 -0
  153. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/profile_time.cpp +0 -0
  154. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/registry.cpp +0 -0
  155. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/roof.cpp +0 -0
  156. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/segments.cpp +0 -0
  157. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/surface.cpp +0 -0
  158. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/text.cpp +0 -0
  159. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/topology.cpp +0 -0
  160. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/builtins/transforms.cpp +0 -0
  161. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/bytecode_compiler.cpp +0 -0
  162. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/bytecode_vm.cpp +0 -0
  163. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/colored_body.cpp +0 -0
  164. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/coverage.cpp +0 -0
  165. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/csg_generate.cpp +0 -0
  166. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/csg_resolve.cpp +0 -0
  167. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/css_colors.cpp +0 -0
  168. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/debug/debug_repl.cpp +0 -0
  169. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/debug_profile.cpp +0 -0
  170. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/eval_context.cpp +0 -0
  171. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/eval_error.cpp +0 -0
  172. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/eval_use.cpp +0 -0
  173. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/export.cpp +0 -0
  174. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/expr_eval.cpp +0 -0
  175. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/format_closure.cpp +0 -0
  176. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/import/dxf_import.cpp +0 -0
  177. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/import/surface_load.cpp +0 -0
  178. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/import/svg_import.cpp +0 -0
  179. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/manifold_cache.cpp +0 -0
  180. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/measure_geometry.cpp +0 -0
  181. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/mesh_check.cpp +0 -0
  182. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/stmt_eval.cpp +0 -0
  183. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/text/font_match.cpp +0 -0
  184. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/text/freetype_font_provider.cpp +0 -0
  185. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/text/text_metrics.cpp +0 -0
  186. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/user_calls.cpp +0 -0
  187. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/utf8.cpp +0 -0
  188. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/value.cpp +0 -0
  189. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/xkcd_colors.cpp +0 -0
  190. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/xkcd_colors.hpp +0 -0
  191. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/src/zip_stored.cpp +0 -0
  192. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/CMakeLists.txt +0 -0
  193. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_booleans.cpp +0 -0
  194. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_bytecode_compiler.cpp +0 -0
  195. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_children_separate.cpp +0 -0
  196. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_cli.cpp +0 -0
  197. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_control_flow.cpp +0 -0
  198. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_coverage.cpp +0 -0
  199. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_csg_tree.cpp +0 -0
  200. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_debug_hooks.cpp +0 -0
  201. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_discretizer.cpp +0 -0
  202. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_dxf_svg_import.cpp +0 -0
  203. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_eval_context.cpp +0 -0
  204. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_export.cpp +0 -0
  205. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_expr_eval.cpp +0 -0
  206. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_extrude_roof.cpp +0 -0
  207. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_feature_detection.cpp +0 -0
  208. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_function_builtins.cpp +0 -0
  209. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_helpers.hpp +0 -0
  210. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_include_cache.cpp +0 -0
  211. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_manifold_cache.cpp +0 -0
  212. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_mesh_check.cpp +0 -0
  213. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_multi_color_merge.cpp +0 -0
  214. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_openscad_parity.cpp +0 -0
  215. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_primitives.cpp +0 -0
  216. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_profile_time.cpp +0 -0
  217. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_profiling.cpp +0 -0
  218. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_python_bindings.py +0 -0
  219. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_render_expr.cpp +0 -0
  220. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_scope_trail.cpp +0 -0
  221. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_scoping.cpp +0 -0
  222. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_surface.cpp +0 -0
  223. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_tail_calls.cpp +0 -0
  224. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_text.cpp +0 -0
  225. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_transforms.cpp +0 -0
  226. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_undef_args.cpp +0 -0
  227. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_unexpected_args.cpp +0 -0
  228. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_use.cpp +0 -0
  229. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_value.cpp +0 -0
  230. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_viewport_params.cpp +0 -0
  231. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tests/test_zip_stored.cpp +0 -0
  232. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tools/cli/CMakeLists.txt +0 -0
  233. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tools/cli/cli_lib.cpp +0 -0
  234. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tools/cli/cli_lib.hpp +0 -0
  235. {openscad_cpp_evaluator-1.45.1 → openscad_cpp_evaluator-1.45.3}/tools/cli/main.cpp +0 -0
@@ -1237,8 +1237,20 @@ grep for `ponytail:`.
1237
1237
  `text_metrics.hpp`'s `measureText`/`textAlignOffset` plus `Evaluator::fontProvider()`) are
1238
1238
  implemented here too.
1239
1239
  - `include/openscad_cpp_evaluator/mesh_import.hpp`, `src/import/mesh_import.cpp` — STL (ASCII +
1240
- binary, exact-match vertex welding)/OBJ/OFF/3MF mesh loaders, returning plain `(verts, tris)` —
1241
- no `Evaluator`/`Value` dependency, so these are reusable from both `import()` contexts below.
1240
+ binary)/OBJ/OFF/3MF mesh loaders, returning plain `(verts, tris)` — no `Evaluator`/`Value`
1241
+ dependency, so these are reusable from both `import()` contexts below. **Every mesh loader welds
1242
+ vertices at exactly the same position** (`weldVertices`, or `appendWelded` for one object of
1243
+ many), so a closed solid written as a triangle soup or with a duplicated vertex list imports
1244
+ closed. The granularity is OpenSCAD 2026.02.01's, checked by running it: the **whole file** for
1245
+ STL, OBJ (across `o`/`g` groups -- two cube halves in separate `o` groups with their own vertex
1246
+ copies import as one closed cube) and OFF (one mesh); **each object** for 3MF and AMF (two open
1247
+ halves in separate objects stay open; OpenSCAD reports each not closed). OpenSCAD welds OBJ at
1248
+ import, but OFF and single-object 3MF only when the mesh becomes a solid (a lone import exported
1249
+ there keeps the raw soup) -- welding at import gives the same solid. Exact positions only: the
1250
+ near-coincident soups (1e-9, 1e-7) OpenSCAD closes through its "repair and reconstruct" fallback
1251
+ stay open here without `repair=true`. `weldVertices` also rejects a face index past the vertex
1252
+ list (OBJ/3MF used to hand one to Manifold unchecked). The 3MF reader skips `<triangles>`, which
1253
+ its `<triangle` search used to read as one degenerate triangle per mesh.
1242
1254
  - `src/import/scene_import.cpp` — AMF, X3D and VRML97 import, the three multi-object formats export
1243
1255
  writes that nothing could read back (current upstream OpenSCAD removed its AMF importer, so all
1244
1256
  three go beyond it). Geometry only, like the loaders above, but each format's scene structure is
@@ -1246,9 +1258,23 @@ grep for `ponytail:`.
1246
1258
  resolves, `ccw FALSE` flips winding, `IndexedFaceSet` polygons fan-triangulate (a last polygon
1247
1259
  without `-1` counts), and X3D's `IndexedTriangleSet`/`TriangleSet` are read. X3D goes through a
1248
1260
  small real XML parser (comments, CDATA, DOCTYPE, entities); VRML through a tokenizer + generic
1249
- node parser that steps over `PROTO`/`EXTERNPROTO`/`ROUTE` (VRML 1.0 is refused by name). AMF's
1250
- `unit` scales to mm, and a zipped AMF (`PK` header) is read through `readStoredZipEntryBySuffix`.
1251
- What is not a mesh — primitives (Box, Sphere, ...), `Inline`, AMF constellations — lands in
1261
+ node parser that steps over `PROTO`/`EXTERNPROTO` declarations and `ROUTE` (VRML 1.0 is refused by
1262
+ name). AMF's
1263
+ `unit` is ignored (OpenSCAD reads millimetres whatever it says), a zipped AMF (`PK` header) is read
1264
+ through `readStoredZipEntryBySuffix`, and each AMF object's vertices are welded by exact position
1265
+ (`appendWelded`), so a closed object written as a triangle soup imports closed, as in
1266
+ OpenSCAD -- per object, never across objects, and never near-coincident vertices. X3D and VRML,
1267
+ which OpenSCAD cannot import, weld each Shape's geometry node the same way (in
1268
+ `emitIndexedFaces`), matching AMF/3MF's per-object rule; a `USE`d Shape is a separate mesh.
1269
+ `Switch` imports only its `whichChoice` child (VRML `choice[k]`, X3D's k-th child element;
1270
+ nothing at -1, the spec default, or out of range) and `LOD` only its first level (VRML `level[0]`,
1271
+ X3D's first child). Prototypes are **not expanded**, but no longer silently: X3D does not walk
1272
+ `ProtoDeclare`/`ExternProtoDeclare` (a declaration's body is a template -- it used to be imported
1273
+ once as plain geometry with `IS` ignored) and counts those and `ProtoInstance` as skipped; VRML
1274
+ records every `PROTO`/`EXTERNPROTO` name and counts an instance of one as `PROTO instance`, without
1275
+ walking its fields. Both reach the user as the usual `skipped what is not a mesh: ...` warning.
1276
+ Expanding them (substituting `IS` fields into a copy of the body per instance) is still to do.
1277
+ What is not imported — primitives (Box, Sphere, ...), `Inline`, prototypes, AMF constellations — lands in
1252
1278
  `LoadedMesh::warnings`, which both `import()` faces emit as `import: '<file>': ...`.
1253
1279
  - `include/openscad_cpp_evaluator/import_builtin.hpp`, `include/openscad_cpp_evaluator/
1254
1280
  zip_stored.hpp` (+ `src/zip_stored.cpp`), `src/builtins/import.cpp` — `import()`'s two faces:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.45.1
3
+ Version: 1.45.3
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT AND OFL-1.1
@@ -0,0 +1,49 @@
1
+ #pragma once
2
+
3
+ #include "openscad_cpp_evaluator/value.hpp"
4
+
5
+ #include <array>
6
+ #include <string>
7
+ #include <vector>
8
+
9
+ namespace oscadeval {
10
+
11
+ // Welded verts + triangle-index list, the shared shape every mesh loader
12
+ // below returns. Mirrors the reference's (verts, tris) tuple.
13
+ struct LoadedMesh {
14
+ std::vector<std::array<double, 3>> verts;
15
+ std::vector<std::array<int, 3>> tris;
16
+ // What the loader read but could not turn into mesh (X3D/VRML primitives,
17
+ // Inline, AMF constellations), for import() to warn about.
18
+ std::vector<std::string> warnings;
19
+ };
20
+
21
+ // Each throws std::runtime_error with a message suitable for wrapping into
22
+ // "import: {what}" on parse/IO failure. OBJ/OFF fan-triangulate >3-gon faces.
23
+ //
24
+ // Every loader merges vertices at exactly the same position, at the
25
+ // granularity OpenSCAD 2026.02.01 does (or, for X3D/VRML, which it cannot
26
+ // import, the same as AMF): the whole file for STL, OBJ and OFF; each
27
+ // object for 3MF and AMF; each Shape's geometry for X3D and VRML. Where the
28
+ // unit is an object, two open halves in separate objects stay open (an
29
+ // OBJ's `o` groups are not objects in this sense: OpenSCAD welds across
30
+ // them). Near-coincident vertices are never merged.
31
+ //
32
+ // weldVertices merges exactly-equal positions; every position keeps its
33
+ // first index. Throws on a face index past the vertex list.
34
+ LoadedMesh weldVertices(const std::vector<std::array<double, 3>>& verts, const std::vector<std::array<int, 3>>& tris);
35
+ // Welds `part` and appends it to `out`, keeping `out`'s earlier objects apart.
36
+ void appendWelded(LoadedMesh& out, const LoadedMesh& part);
37
+ LoadedMesh loadStl(const std::string& path);
38
+ LoadedMesh loadObj(const std::string& path);
39
+ LoadedMesh loadOff(const std::string& path);
40
+ LoadedMesh loadThreeMf(const std::string& path);
41
+ // scene_import.cpp: the three multi-object formats export writes. AMF (plain
42
+ // or zipped, `unit` ignored as OpenSCAD ignores it), X3D and VRML97
43
+ // (Transforms applied, DEF/USE resolved, polygons fan-triangulated).
44
+ // Geometry only, like the rest.
45
+ LoadedMesh loadAmf(const std::string& path);
46
+ LoadedMesh loadX3d(const std::string& path);
47
+ LoadedMesh loadVrml(const std::string& path);
48
+
49
+ } // namespace oscadeval
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.45.1"
7
+ version = "1.45.3"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -3,6 +3,7 @@
3
3
  #include "openscad_cpp_evaluator/zip_stored.hpp"
4
4
 
5
5
  #include <algorithm>
6
+ #include <cctype>
6
7
  #include <cstdint>
7
8
  #include <cstring>
8
9
  #include <fstream>
@@ -57,9 +58,22 @@ std::vector<std::string> splitWs(const std::string& s) {
57
58
  return out;
58
59
  }
59
60
 
60
- // Exact-match vertex welding (STL has no shared-index concept; each
61
- // triangle carries its own private corner copies) -- matches
62
- // _weld_stl_vertices's np.unique(axis=0) exact-equality behavior.
61
+ std::string attrValue(std::string_view tag, std::string_view attrName) {
62
+ const std::string needle = std::string(attrName) + "=\"";
63
+ const size_t pos = tag.find(needle);
64
+ if (pos == std::string_view::npos) return "0";
65
+ const size_t start = pos + needle.size();
66
+ const size_t end = tag.find('"', start);
67
+ if (end == std::string_view::npos) return "0";
68
+ return std::string(tag.substr(start, end - start));
69
+ }
70
+
71
+ } // namespace
72
+
73
+ // Exact-match vertex welding: every position keeps its first index. Only
74
+ // bit-identical coordinates merge; near-coincident ones stay apart (that is
75
+ // repair=true's job). A face index past the vertex list is an error here
76
+ // rather than a bad read.
63
77
  LoadedMesh weldVertices(const std::vector<std::array<double, 3>>& verts, const std::vector<std::array<int, 3>>& tris) {
64
78
  std::map<std::array<double, 3>, int> seen;
65
79
  LoadedMesh out;
@@ -75,23 +89,24 @@ LoadedMesh weldVertices(const std::vector<std::array<double, 3>>& verts, const s
75
89
  remap[i] = idx;
76
90
  }
77
91
  }
92
+ const auto at = [&](int i) {
93
+ if (i < 0 || static_cast<size_t>(i) >= verts.size())
94
+ throw std::runtime_error("face index " + std::to_string(i) + " is past its " + std::to_string(verts.size()) +
95
+ " vertices");
96
+ return remap[static_cast<size_t>(i)];
97
+ };
78
98
  out.tris.reserve(tris.size());
79
- for (const auto& t : tris) out.tris.push_back({remap[t[0]], remap[t[1]], remap[t[2]]});
99
+ for (const auto& t : tris) out.tris.push_back({at(t[0]), at(t[1]), at(t[2])});
80
100
  return out;
81
101
  }
82
102
 
83
- std::string attrValue(std::string_view tag, std::string_view attrName) {
84
- const std::string needle = std::string(attrName) + "=\"";
85
- const size_t pos = tag.find(needle);
86
- if (pos == std::string_view::npos) return "0";
87
- const size_t start = pos + needle.size();
88
- const size_t end = tag.find('"', start);
89
- if (end == std::string_view::npos) return "0";
90
- return std::string(tag.substr(start, end - start));
103
+ void appendWelded(LoadedMesh& out, const LoadedMesh& part) {
104
+ const LoadedMesh w = weldVertices(part.verts, part.tris);
105
+ const int base = static_cast<int>(out.verts.size());
106
+ out.verts.insert(out.verts.end(), w.verts.begin(), w.verts.end());
107
+ for (const auto& t : w.tris) out.tris.push_back({base + t[0], base + t[1], base + t[2]});
91
108
  }
92
109
 
93
- } // namespace
94
-
95
110
  LoadedMesh loadStl(const std::string& path) {
96
111
  const std::vector<uint8_t> bytes = readWholeFile(path);
97
112
  // OpenSCAD's wording for every file it cannot read as STL.
@@ -162,7 +177,11 @@ LoadedMesh loadObj(const std::string& path) {
162
177
  for (size_t i = 1; i + 1 < idx.size(); ++i) out.tris.push_back({idx[0], idx[i], idx[i + 1]});
163
178
  }
164
179
  }
165
- return out;
180
+ // OpenSCAD welds an OBJ's exactly-equal vertices across the whole file,
181
+ // `o`/`g` boundaries included (checked, 2026.02.01: two halves of a cube
182
+ // in separate `o` objects, each with its own vertex copies, import as
183
+ // one closed cube).
184
+ return weldVertices(out.verts, out.tris);
166
185
  }
167
186
 
168
187
  LoadedMesh loadOff(const std::string& path) {
@@ -231,7 +250,10 @@ LoadedMesh loadOff(const std::string& path) {
231
250
  }
232
251
  for (int k = 1; k + 1 < cnt; ++k) out.tris.push_back({faceIdx[0], faceIdx[static_cast<size_t>(k)], faceIdx[static_cast<size_t>(k) + 1]});
233
252
  }
234
- return out;
253
+ // An OFF is one mesh, and OpenSCAD merges its exactly-equal vertices
254
+ // when the mesh becomes a solid (checked, 2026.02.01: a triangle soup
255
+ // or a duplicated vertex list takes part in CSG as a closed solid).
256
+ return weldVertices(out.verts, out.tris);
235
257
  }
236
258
 
237
259
  LoadedMesh loadThreeMf(const std::string& path) {
@@ -248,26 +270,33 @@ LoadedMesh loadThreeMf(const std::string& path) {
248
270
  const std::string_view segment(xml.data() + meshStart, meshEnd - meshStart);
249
271
  searchPos = meshEnd + 7;
250
272
 
251
- const int base = static_cast<int>(out.verts.size());
273
+ LoadedMesh obj;
252
274
  for (size_t p = 0;;) {
253
275
  const size_t vStart = segment.find("<vertex", p);
254
276
  if (vStart == std::string_view::npos) break;
255
277
  const size_t vEnd = segment.find('>', vStart);
256
278
  if (vEnd == std::string_view::npos) break;
257
279
  const std::string_view tag = segment.substr(vStart, vEnd - vStart + 1);
258
- out.verts.push_back({std::stod(attrValue(tag, "x")), std::stod(attrValue(tag, "y")), std::stod(attrValue(tag, "z"))});
280
+ obj.verts.push_back({std::stod(attrValue(tag, "x")), std::stod(attrValue(tag, "y")), std::stod(attrValue(tag, "z"))});
259
281
  p = vEnd + 1;
260
282
  }
261
283
  for (size_t p = 0;;) {
262
284
  const size_t tStart = segment.find("<triangle", p);
263
285
  if (tStart == std::string_view::npos) break;
286
+ if (tStart + 9 < segment.size() && std::isalpha(static_cast<unsigned char>(segment[tStart + 9]))) { // <triangles>, not a triangle
287
+ p = tStart + 9;
288
+ continue;
289
+ }
264
290
  const size_t tEnd = segment.find('>', tStart);
265
291
  if (tEnd == std::string_view::npos) break;
266
292
  const std::string_view tag = segment.substr(tStart, tEnd - tStart + 1);
267
- out.tris.push_back({base + std::stoi(attrValue(tag, "v1")), base + std::stoi(attrValue(tag, "v2")),
268
- base + std::stoi(attrValue(tag, "v3"))});
293
+ obj.tris.push_back({std::stoi(attrValue(tag, "v1")), std::stoi(attrValue(tag, "v2")), std::stoi(attrValue(tag, "v3"))});
269
294
  p = tEnd + 1;
270
295
  }
296
+ // Welded per object, as OpenSCAD makes each 3MF object a solid on
297
+ // its own (checked, 2026.02.01): a soup object closes, but two open
298
+ // halves in separate objects stay open.
299
+ appendWelded(out, obj);
271
300
  }
272
301
  return out;
273
302
  }
@@ -5,11 +5,24 @@
5
5
  // through import()). Each format's scene structure IS honoured, though:
6
6
  // X3D/VRML Transforms (translation, rotation, scale, center,
7
7
  // scaleOrientation) nest and apply, DEF/USE reuse resolves, `ccw FALSE`
8
- // flips winding, and polygons are fan-triangulated. AMF's `unit` scales to
9
- // millimetres, and a zipped AMF is read like 3MF.
8
+ // flips winding, and polygons are fan-triangulated. AMF's `unit` is
9
+ // ignored, as OpenSCAD ignores it (its numbers are read as millimetres),
10
+ // and a zipped AMF is read like 3MF.
11
+ //
12
+ // Vertices at exactly the same position are merged (weldVertices) within
13
+ // each AMF object and within each X3D/VRML Shape's geometry, never across
14
+ // them -- so a closed object written as a triangle soup imports closed, and
15
+ // two open halves in separate objects stay open. AMF's granularity is
16
+ // OpenSCAD's (checked, 2026.02.01); OpenSCAD cannot import X3D or VRML, so
17
+ // theirs follows AMF and 3MF, where each object is a solid on its own.
18
+ //
19
+ // Switch imports only its whichChoice child (none at the default -1) and
20
+ // LOD only its first level.
10
21
  //
11
22
  // What is skipped says so, through LoadedMesh::warnings: X3D/VRML
12
- // primitives (Box, Sphere, ...), Inline references, and AMF constellations.
23
+ // primitives (Box, Sphere, ...), Inline references, prototypes (not
24
+ // expanded: X3D ProtoDeclare/ExternProtoDeclare/ProtoInstance, VRML
25
+ // instances of a PROTO/EXTERNPROTO type), and AMF constellations.
13
26
  // None of them is a mesh, and inventing one would be a different model from
14
27
  // the file.
15
28
  #include "openscad_cpp_evaluator/mesh_import.hpp"
@@ -143,14 +156,14 @@ Mat transformNode(const std::vector<double>& t, const std::vector<double>& r, co
143
156
  void emitIndexedFaces(LoadedMesh& out, const std::vector<double>& points, const std::vector<double>& index,
144
157
  const Mat& m, bool ccw, const std::string& what) {
145
158
  const int n = static_cast<int>(points.size() / 3);
146
- const int base = static_cast<int>(out.verts.size());
147
- for (int i = 0; i < n; ++i) out.verts.push_back(apply(m, points[i * 3], points[i * 3 + 1], points[i * 3 + 2]));
159
+ LoadedMesh part;
160
+ for (int i = 0; i < n; ++i) part.verts.push_back(apply(m, points[i * 3], points[i * 3 + 1], points[i * 3 + 2]));
148
161
  std::vector<int> poly;
149
162
  const auto flush = [&]() {
150
163
  for (size_t k = 1; k + 1 < poly.size(); ++k) {
151
- std::array<int, 3> tri{base + poly[0], base + poly[k], base + poly[k + 1]};
164
+ std::array<int, 3> tri{poly[0], poly[k], poly[k + 1]};
152
165
  if (!ccw) std::swap(tri[1], tri[2]);
153
- out.tris.push_back(tri);
166
+ part.tris.push_back(tri);
154
167
  }
155
168
  poly.clear();
156
169
  };
@@ -165,6 +178,9 @@ void emitIndexedFaces(LoadedMesh& out, const std::vector<double>& points, const
165
178
  poly.push_back(i);
166
179
  }
167
180
  flush();
181
+ // One geometry node is one Shape's mesh: welded on its own, like an AMF
182
+ // or 3MF object.
183
+ appendWelded(out, part);
168
184
  }
169
185
 
170
186
  // IndexedTriangleSet / TriangleSet: every three indices (or points) are one
@@ -331,6 +347,17 @@ public:
331
347
  for (const auto& c : n.children) geometry(resolve(*c), m);
332
348
  } else if (n.name == "Inline") {
333
349
  ++skipped["Inline"];
350
+ } else if (n.name == "Switch") {
351
+ // Only the chosen child; whichChoice defaults to -1, nothing.
352
+ const std::vector<double> w = nums(n, "whichChoice");
353
+ const long k = w.empty() ? -1 : static_cast<long>(w[0]);
354
+ if (k >= 0 && static_cast<size_t>(k) < n.children.size()) walk(*n.children[static_cast<size_t>(k)], m);
355
+ } else if (n.name == "LOD") {
356
+ if (!n.children.empty()) walk(*n.children[0], m); // the first, most detailed level
357
+ } else if (n.name == "ProtoDeclare" || n.name == "ExternProtoDeclare" || n.name == "ProtoInstance") {
358
+ // Prototypes are not expanded: a declaration's body is a template,
359
+ // not geometry, and an instance has nothing to place without it.
360
+ ++skipped[n.name];
334
361
  } else {
335
362
  for (const auto& c : n.children) walk(*c, m);
336
363
  }
@@ -400,6 +427,9 @@ class VrmlParser {
400
427
  public:
401
428
  explicit VrmlParser(const std::string& text) { tokenize(text); }
402
429
 
430
+ // Node type names a PROTO or EXTERNPROTO declares.
431
+ std::set<std::string> protos;
432
+
403
433
  std::vector<VNodePtr> parseScene() {
404
434
  std::vector<VNodePtr> nodes;
405
435
  while (i_ < toks_.size()) {
@@ -407,6 +437,7 @@ public:
407
437
  if (t == "ROUTE") {
408
438
  i_ += 4; // ROUTE a.b TO c.d
409
439
  } else if (t == "PROTO" || t == "EXTERNPROTO") {
440
+ protos.insert(peek(1));
410
441
  i_ += 2;
411
442
  skipBalanced("[", "]");
412
443
  if (t == "PROTO") {
@@ -538,10 +569,23 @@ class VrmlWalker {
538
569
  public:
539
570
  LoadedMesh out;
540
571
  std::map<std::string, int> skipped;
572
+ std::set<std::string> protos;
541
573
 
542
574
  void walk(const VNodePtr& n, const Mat& m) {
543
575
  if (!n) return;
544
- if (n->type == "Transform") {
576
+ if (protos.count(n->type)) {
577
+ // Prototypes are not expanded, so an instance's geometry is lost.
578
+ ++skipped["PROTO instance"];
579
+ } else if (n->type == "Switch") {
580
+ // Only the chosen child; whichChoice defaults to -1, nothing.
581
+ const std::vector<double> w = nums(*n, "whichChoice");
582
+ const long k = w.empty() ? -1 : static_cast<long>(w[0]);
583
+ const auto& choice = kids(*n, "choice");
584
+ if (k >= 0 && static_cast<size_t>(k) < choice.size()) walk(choice[static_cast<size_t>(k)], m);
585
+ } else if (n->type == "LOD") {
586
+ const auto& level = kids(*n, "level");
587
+ if (!level.empty()) walk(level[0], m); // the first, most detailed level
588
+ } else if (n->type == "Transform") {
545
589
  const Mat t = transformNode(nums(*n, "translation"), nums(*n, "rotation"), nums(*n, "scale"),
546
590
  nums(*n, "center"), nums(*n, "scaleOrientation"));
547
591
  for (const auto& c : kids(*n, "children")) walk(c, mul(m, t));
@@ -550,8 +594,8 @@ public:
550
594
  } else if (n->type == "Inline") {
551
595
  ++skipped["Inline"];
552
596
  } else {
553
- // Group, Anchor, Billboard, Collision, Switch (choice), LOD (level)
554
- // and anything else holding nodes: walk every node-valued field.
597
+ // Group, Anchor, Billboard, Collision and anything else holding
598
+ // nodes: walk every node-valued field.
555
599
  for (const auto& [name, f] : n->fields) {
556
600
  for (const auto& c : f.nodes) walk(c, m);
557
601
  }
@@ -653,13 +697,13 @@ LoadedMesh loadAmf(const std::string& path) {
653
697
  LoadedMesh out;
654
698
  for (std::string_view object : segments(xml, "object")) {
655
699
  const std::string_view verticesBlock = elementText(object, "vertices");
656
- const int base = static_cast<int>(out.verts.size());
700
+ LoadedMesh obj;
657
701
  for (std::string_view vertex : segments(verticesBlock, "vertex")) {
658
702
  const std::string_view c = elementText(vertex, "coordinates");
659
- out.verts.push_back({amfNumber(elementText(c, "x"), "x") * unit, amfNumber(elementText(c, "y"), "y") * unit,
703
+ obj.verts.push_back({amfNumber(elementText(c, "x"), "x") * unit, amfNumber(elementText(c, "y"), "y") * unit,
660
704
  amfNumber(elementText(c, "z"), "z") * unit});
661
705
  }
662
- const int count = static_cast<int>(out.verts.size()) - base;
706
+ const int count = static_cast<int>(obj.verts.size());
663
707
  for (std::string_view volume : segments(object, "volume")) {
664
708
  for (std::string_view tri : segments(volume, "triangle")) {
665
709
  std::array<int, 3> t{};
@@ -669,11 +713,17 @@ LoadedMesh loadAmf(const std::string& path) {
669
713
  if (i < 0 || i >= count)
670
714
  throw std::runtime_error("AMF: triangle vertex " + std::to_string(i) + " is past its object's " +
671
715
  std::to_string(count) + " vertices");
672
- t[k] = base + i;
716
+ t[k] = i;
673
717
  }
674
- out.tris.push_back(t);
718
+ obj.tris.push_back(t);
675
719
  }
676
720
  }
721
+ // OpenSCAD treats vertices at exactly the same position as one
722
+ // (checked, 2026.02.01): a closed object written as a triangle soup,
723
+ // or with its vertices listed twice, imports as a closed solid.
724
+ // Within each object only -- two open halves in separate objects
725
+ // stay open there too. Near-coincident vertices are not merged.
726
+ appendWelded(out, obj);
677
727
  }
678
728
  if (xml.find("<constellation") != std::string::npos) {
679
729
  out.warnings.push_back("AMF constellations are not applied: each object is imported where it was modeled");
@@ -697,6 +747,7 @@ LoadedMesh loadVrml(const std::string& path) {
697
747
  VrmlParser parser(text);
698
748
  const std::vector<VNodePtr> scene = parser.parseScene();
699
749
  VrmlWalker walker;
750
+ walker.protos = parser.protos;
700
751
  for (const VNodePtr& n : scene) walker.walk(n, identity());
701
752
  if (!walker.skipped.empty()) walker.out.warnings.push_back(skippedWarning(walker.skipped));
702
753
  return std::move(walker.out);