openscad-cpp-evaluator 1.45.2__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.
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/CLAUDE.md +29 -6
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/mesh_import.hpp +17 -7
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/import/mesh_import.cpp +39 -10
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/import/scene_import.cpp +56 -14
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_import_export.cpp +193 -2
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/uv.lock +1 -1
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/.gitignore +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/.gitmodules +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/LICENSE +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/README.md +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/cmake/patch_manifold.cmake +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/LICENSE +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/.DS_Store +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_profile_time.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/resources/fonts/LiberationSans-Bold.ttf +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/resources/fonts/LiberationSans-Italic.ttf +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/profile_time.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/coverage.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/format_closure.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/utf8.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/xkcd_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/xkcd_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_coverage.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_discretizer.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_feature_detection.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_include_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_openscad_parity.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_profile_time.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_undef_args.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-1.45.2 → 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
|
|
1241
|
-
|
|
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,12 +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
|
|
1261
|
+
node parser that steps over `PROTO`/`EXTERNPROTO` declarations and `ROUTE` (VRML 1.0 is refused by
|
|
1262
|
+
name). AMF's
|
|
1250
1263
|
`unit` is ignored (OpenSCAD reads millimetres whatever it says), a zipped AMF (`PK` header) is read
|
|
1251
1264
|
through `readStoredZipEntryBySuffix`, and each AMF object's vertices are welded by exact position
|
|
1252
|
-
(`
|
|
1253
|
-
OpenSCAD -- per object, never across objects, and never near-coincident vertices.
|
|
1254
|
-
|
|
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
|
|
1255
1278
|
`LoadedMesh::warnings`, which both `import()` faces emit as `import: '<file>': ...`.
|
|
1256
1279
|
- `include/openscad_cpp_evaluator/import_builtin.hpp`, `include/openscad_cpp_evaluator/
|
|
1257
1280
|
zip_stored.hpp` (+ `src/zip_stored.cpp`), `src/builtins/import.cpp` — `import()`'s two faces:
|
|
@@ -19,19 +19,29 @@ struct LoadedMesh {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
// Each throws std::runtime_error with a message suitable for wrapping into
|
|
22
|
-
// "import: {what}" on parse/IO failure.
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
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.
|
|
27
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);
|
|
28
37
|
LoadedMesh loadStl(const std::string& path);
|
|
29
38
|
LoadedMesh loadObj(const std::string& path);
|
|
30
39
|
LoadedMesh loadOff(const std::string& path);
|
|
31
40
|
LoadedMesh loadThreeMf(const std::string& path);
|
|
32
41
|
// scene_import.cpp: the three multi-object formats export writes. AMF (plain
|
|
33
|
-
// or zipped, `unit`
|
|
34
|
-
// DEF/USE resolved, polygons fan-triangulated).
|
|
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.
|
|
35
45
|
LoadedMesh loadAmf(const std::string& path);
|
|
36
46
|
LoadedMesh loadX3d(const std::string& path);
|
|
37
47
|
LoadedMesh loadVrml(const std::string& path);
|
|
@@ -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>
|
|
@@ -69,9 +70,10 @@ std::string attrValue(std::string_view tag, std::string_view attrName) {
|
|
|
69
70
|
|
|
70
71
|
} // namespace
|
|
71
72
|
|
|
72
|
-
// Exact-match vertex welding
|
|
73
|
-
//
|
|
74
|
-
//
|
|
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.
|
|
75
77
|
LoadedMesh weldVertices(const std::vector<std::array<double, 3>>& verts, const std::vector<std::array<int, 3>>& tris) {
|
|
76
78
|
std::map<std::array<double, 3>, int> seen;
|
|
77
79
|
LoadedMesh out;
|
|
@@ -87,11 +89,24 @@ LoadedMesh weldVertices(const std::vector<std::array<double, 3>>& verts, const s
|
|
|
87
89
|
remap[i] = idx;
|
|
88
90
|
}
|
|
89
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
|
+
};
|
|
90
98
|
out.tris.reserve(tris.size());
|
|
91
|
-
for (const auto& t : tris) out.tris.push_back({
|
|
99
|
+
for (const auto& t : tris) out.tris.push_back({at(t[0]), at(t[1]), at(t[2])});
|
|
92
100
|
return out;
|
|
93
101
|
}
|
|
94
102
|
|
|
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]});
|
|
108
|
+
}
|
|
109
|
+
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
9
|
-
//
|
|
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,
|
|
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
|
-
|
|
147
|
-
for (int i = 0; i < n; ++i)
|
|
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{
|
|
164
|
+
std::array<int, 3> tri{poly[0], poly[k], poly[k + 1]};
|
|
152
165
|
if (!ccw) std::swap(tri[1], tri[2]);
|
|
153
|
-
|
|
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
|
|
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
|
|
554
|
-
//
|
|
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
|
}
|
|
@@ -679,10 +723,7 @@ LoadedMesh loadAmf(const std::string& path) {
|
|
|
679
723
|
// or with its vertices listed twice, imports as a closed solid.
|
|
680
724
|
// Within each object only -- two open halves in separate objects
|
|
681
725
|
// stay open there too. Near-coincident vertices are not merged.
|
|
682
|
-
|
|
683
|
-
const int base = static_cast<int>(out.verts.size());
|
|
684
|
-
out.verts.insert(out.verts.end(), obj.verts.begin(), obj.verts.end());
|
|
685
|
-
for (const auto& t : obj.tris) out.tris.push_back({base + t[0], base + t[1], base + t[2]});
|
|
726
|
+
appendWelded(out, obj);
|
|
686
727
|
}
|
|
687
728
|
if (xml.find("<constellation") != std::string::npos) {
|
|
688
729
|
out.warnings.push_back("AMF constellations are not applied: each object is imported where it was modeled");
|
|
@@ -706,6 +747,7 @@ LoadedMesh loadVrml(const std::string& path) {
|
|
|
706
747
|
VrmlParser parser(text);
|
|
707
748
|
const std::vector<VNodePtr> scene = parser.parseScene();
|
|
708
749
|
VrmlWalker walker;
|
|
750
|
+
walker.protos = parser.protos;
|
|
709
751
|
for (const VNodePtr& n : scene) walker.walk(n, identity());
|
|
710
752
|
if (!walker.skipped.empty()) walker.out.warnings.push_back(skippedWarning(walker.skipped));
|
|
711
753
|
return std::move(walker.out);
|
{openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/tests/test_import_export.cpp
RENAMED
|
@@ -227,6 +227,72 @@ TEST(ImportModuleContext, VrmlIndexPastItsPointsErrors) {
|
|
|
227
227
|
|
|
228
228
|
namespace {
|
|
229
229
|
|
|
230
|
+
// The unit cube translated by `x` as one X3D / VRML node.
|
|
231
|
+
std::string x3dCubeAt(int x) {
|
|
232
|
+
return "<Transform translation=\"" + std::to_string(x) + " 0 0\"><Shape><IndexedFaceSet coordIndex=\"" +
|
|
233
|
+
kCubeQuads + "\"><Coordinate point=\"" + kCubePoints + "\"/></IndexedFaceSet></Shape></Transform>";
|
|
234
|
+
}
|
|
235
|
+
std::string wrlCubeAt(int x) {
|
|
236
|
+
return "Transform { translation " + std::to_string(x) +
|
|
237
|
+
" 0 0 children [ Shape { geometry IndexedFaceSet { coord Coordinate { point [ " + kCubePoints +
|
|
238
|
+
" ] } coordIndex [ " + kCubeQuads + " ] } } ] }\n";
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
} // namespace
|
|
242
|
+
|
|
243
|
+
// Switch draws only its whichChoice child, and nothing at -1 (the default);
|
|
244
|
+
// LOD draws only its first level. Every child was imported before: 7 cubes.
|
|
245
|
+
// Here: Switch 1 -> the cube at 3; Switch -1 and a bare Switch -> nothing;
|
|
246
|
+
// LOD -> the cube at 9. Two cubes, x 3..10.
|
|
247
|
+
TEST(ImportModuleContext, X3dSwitchAndLodImportOnlyTheSelectedChild) {
|
|
248
|
+
const std::string x3d = "<X3D><Scene><Switch whichChoice=\"1\">" + x3dCubeAt(0) + x3dCubeAt(3) +
|
|
249
|
+
"</Switch><Switch whichChoice=\"-1\">" + x3dCubeAt(6) + "</Switch><Switch>" +
|
|
250
|
+
x3dCubeAt(6) + "</Switch><Switch whichChoice=\"5\">" + x3dCubeAt(6) + "</Switch><LOD>" +
|
|
251
|
+
x3dCubeAt(9) + x3dCubeAt(12) + "</LOD></Scene></X3D>";
|
|
252
|
+
const Imported r = importText("switch.x3d", x3d);
|
|
253
|
+
EXPECT_NEAR(r.volume, 2.0, 1e-9);
|
|
254
|
+
EXPECT_NEAR(r.box.min.x, 3, 1e-9);
|
|
255
|
+
EXPECT_NEAR(r.box.max.x, 10, 1e-9);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
TEST(ImportModuleContext, VrmlSwitchAndLodImportOnlyTheSelectedChild) {
|
|
259
|
+
const std::string wrl = "#VRML V2.0 utf8\nSwitch { whichChoice 1 choice [ " + wrlCubeAt(0) + wrlCubeAt(3) +
|
|
260
|
+
" ] }\nSwitch { whichChoice -1 choice [ " + wrlCubeAt(6) + " ] }\nSwitch { choice [ " +
|
|
261
|
+
wrlCubeAt(6) + " ] }\nSwitch { whichChoice 5 choice [ " + wrlCubeAt(6) +
|
|
262
|
+
" ] }\nLOD { level [ " + wrlCubeAt(9) + wrlCubeAt(12) + " ] }\n";
|
|
263
|
+
const Imported r = importText("switch.wrl", wrl);
|
|
264
|
+
EXPECT_NEAR(r.volume, 2.0, 1e-9);
|
|
265
|
+
EXPECT_NEAR(r.box.min.x, 3, 1e-9);
|
|
266
|
+
EXPECT_NEAR(r.box.max.x, 10, 1e-9);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Prototypes are not expanded. An X3D ProtoDeclare's body is a template, so
|
|
270
|
+
// it is no longer imported as if it were geometry (it was: a cube at the
|
|
271
|
+
// origin), and the declaration and the instance are both reported.
|
|
272
|
+
TEST(ImportModuleContext, X3dPrototypesAreSkippedWithAWarning) {
|
|
273
|
+
const std::string x3d = "<X3D><Scene><ProtoDeclare name=\"Box1\"><ProtoInterface><field name=\"t\" "
|
|
274
|
+
"type=\"SFVec3f\" accessType=\"initializeOnly\" value=\"0 0 0\"/></ProtoInterface><ProtoBody>" +
|
|
275
|
+
x3dCubeAt(0) + "</ProtoBody></ProtoDeclare><ProtoInstance name=\"Box1\"><fieldValue name=\"t\" "
|
|
276
|
+
"value=\"5 0 0\"/></ProtoInstance>" + x3dCubeAt(20) + "</Scene></X3D>";
|
|
277
|
+
const Imported r = importText("proto.x3d", x3d);
|
|
278
|
+
EXPECT_NEAR(r.volume, 1.0, 1e-9);
|
|
279
|
+
EXPECT_NEAR(r.box.min.x, 20, 1e-9);
|
|
280
|
+
EXPECT_TRUE(anyContains(r.messages, "skipped what is not a mesh: 1 ProtoDeclare, 1 ProtoInstance"))
|
|
281
|
+
<< ::testing::PrintToString(r.messages);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// A VRML PROTO instance's geometry is lost; it used to be lost silently.
|
|
285
|
+
TEST(ImportModuleContext, VrmlProtoInstanceIsReported) {
|
|
286
|
+
const std::string wrl = "#VRML V2.0 utf8\nPROTO Box1 [ field SFVec3f t 0 0 0 ] { " + wrlCubeAt(0) +
|
|
287
|
+
" }\nEXTERNPROTO Far [ ] \"far.wrl\"\nBox1 { t 5 0 0 }\nFar { }\n" + wrlCubeAt(20);
|
|
288
|
+
const Imported r = importText("proto.wrl", wrl);
|
|
289
|
+
EXPECT_NEAR(r.volume, 1.0, 1e-9);
|
|
290
|
+
EXPECT_TRUE(anyContains(r.messages, "skipped what is not a mesh: 2 PROTO instance"))
|
|
291
|
+
<< ::testing::PrintToString(r.messages);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
namespace {
|
|
295
|
+
|
|
230
296
|
std::string inchCubeAmf() {
|
|
231
297
|
const int tris[12][3] = {{0, 3, 2}, {0, 2, 1}, {4, 5, 6}, {4, 6, 7}, {0, 1, 5}, {0, 5, 4},
|
|
232
298
|
{1, 2, 6}, {1, 6, 5}, {2, 3, 7}, {2, 7, 6}, {3, 0, 4}, {3, 4, 7}};
|
|
@@ -245,8 +311,7 @@ std::string inchCubeAmf() {
|
|
|
245
311
|
|
|
246
312
|
} // namespace
|
|
247
313
|
|
|
248
|
-
// unit="inch"
|
|
249
|
-
// says so.
|
|
314
|
+
// unit="inch" is ignored; a constellation is not applied, and says so.
|
|
250
315
|
TEST(ImportModuleContext, AmfUnitIsIgnoredAsInOpenSCAD) {
|
|
251
316
|
// OpenSCAD reads an AMF's numbers as millimetres whatever its unit says
|
|
252
317
|
// (checked, 2026.02.01: a unit="inch" unit cube imports as a unit cube),
|
|
@@ -360,6 +425,132 @@ TEST(ImportModuleContext, AmfWeldsWithinAnObjectOnly) {
|
|
|
360
425
|
EXPECT_TRUE(anyContains(r.messages, "not a closed solid")) << ::testing::PrintToString(r.messages);
|
|
361
426
|
}
|
|
362
427
|
|
|
428
|
+
namespace {
|
|
429
|
+
|
|
430
|
+
// `objects` as a triangle soup (every triangle with its own three vertex
|
|
431
|
+
// copies) in `ext`'s own notion of an object: an `o` group for OBJ, an
|
|
432
|
+
// <object> for 3MF, a Shape for X3D/VRML. OFF has no objects, so they are
|
|
433
|
+
// simply concatenated.
|
|
434
|
+
Imported importSoup(const std::string& ext, const std::vector<std::vector<Tri>>& objects) {
|
|
435
|
+
const auto num = [](double d) { return std::to_string(d); };
|
|
436
|
+
std::string text;
|
|
437
|
+
if (ext == "off") {
|
|
438
|
+
size_t n = 0;
|
|
439
|
+
for (const auto& o : objects) n += o.size();
|
|
440
|
+
text = "OFF\n" + std::to_string(3 * n) + " " + std::to_string(n) + " 0\n";
|
|
441
|
+
for (const auto& o : objects)
|
|
442
|
+
for (const Tri& t : o)
|
|
443
|
+
for (const auto& p : t) text += num(p[0]) + " " + num(p[1]) + " " + num(p[2]) + "\n";
|
|
444
|
+
for (size_t i = 0; i < n; ++i)
|
|
445
|
+
text += "3 " + std::to_string(3 * i) + " " + std::to_string(3 * i + 1) + " " + std::to_string(3 * i + 2) + "\n";
|
|
446
|
+
} else if (ext == "obj") {
|
|
447
|
+
size_t base = 1;
|
|
448
|
+
for (size_t k = 0; k < objects.size(); ++k) {
|
|
449
|
+
text += "o part" + std::to_string(k) + "\n";
|
|
450
|
+
for (const Tri& t : objects[k])
|
|
451
|
+
for (const auto& p : t) text += "v " + num(p[0]) + " " + num(p[1]) + " " + num(p[2]) + "\n";
|
|
452
|
+
for (size_t i = 0; i < objects[k].size(); ++i, base += 3)
|
|
453
|
+
text += "f " + std::to_string(base) + " " + std::to_string(base + 1) + " " + std::to_string(base + 2) + "\n";
|
|
454
|
+
}
|
|
455
|
+
} else if (ext == "3mf") {
|
|
456
|
+
text = "<?xml version=\"1.0\"?><model unit=\"millimeter\"><resources>";
|
|
457
|
+
for (size_t k = 0; k < objects.size(); ++k) {
|
|
458
|
+
text += "<object id=\"" + std::to_string(k + 1) + "\" type=\"model\"><mesh><vertices>";
|
|
459
|
+
for (const Tri& t : objects[k])
|
|
460
|
+
for (const auto& p : t) text += "<vertex x=\"" + num(p[0]) + "\" y=\"" + num(p[1]) + "\" z=\"" + num(p[2]) + "\"/>";
|
|
461
|
+
text += "</vertices><triangles>";
|
|
462
|
+
for (size_t i = 0; i < objects[k].size(); ++i)
|
|
463
|
+
text += "<triangle v1=\"" + std::to_string(3 * i) + "\" v2=\"" + std::to_string(3 * i + 1) + "\" v3=\"" +
|
|
464
|
+
std::to_string(3 * i + 2) + "\"/>";
|
|
465
|
+
text += "</triangles></mesh></object>";
|
|
466
|
+
}
|
|
467
|
+
text += "</resources><build/></model>";
|
|
468
|
+
const auto path = tempPath("soup.3mf");
|
|
469
|
+
writeDeflateZip(path.string(), {ZipEntry{"3D/3dmodel.model", std::vector<uint8_t>(text.begin(), text.end())}});
|
|
470
|
+
Imported out;
|
|
471
|
+
Evaluated e = evalSrc("import(\"" + path.generic_string() + "\");",
|
|
472
|
+
[&](const std::string& m) { out.messages.push_back(m); });
|
|
473
|
+
std::filesystem::remove(path);
|
|
474
|
+
if (!e.bodies.empty() && e.bodies[0].body) out.volume = e.bodies[0].body->Volume();
|
|
475
|
+
return out;
|
|
476
|
+
} else {
|
|
477
|
+
const bool x3d = ext == "x3d";
|
|
478
|
+
text = x3d ? "<?xml version=\"1.0\"?><X3D><Scene>" : "#VRML V2.0 utf8\n";
|
|
479
|
+
for (const auto& o : objects) {
|
|
480
|
+
std::string pts, idx;
|
|
481
|
+
for (size_t i = 0; i < o.size(); ++i) {
|
|
482
|
+
for (const auto& p : o[i]) pts += num(p[0]) + " " + num(p[1]) + " " + num(p[2]) + ", ";
|
|
483
|
+
idx += std::to_string(3 * i) + " " + std::to_string(3 * i + 1) + " " + std::to_string(3 * i + 2) + " -1 ";
|
|
484
|
+
}
|
|
485
|
+
text += x3d ? "<Shape><IndexedFaceSet coordIndex=\"" + idx + "\"><Coordinate point=\"" + pts +
|
|
486
|
+
"\"/></IndexedFaceSet></Shape>"
|
|
487
|
+
: "Shape { geometry IndexedFaceSet { coord Coordinate { point [ " + pts + "] } coordIndex [ " +
|
|
488
|
+
idx + "] } }\n";
|
|
489
|
+
}
|
|
490
|
+
if (x3d) text += "</Scene></X3D>";
|
|
491
|
+
}
|
|
492
|
+
return importText("soup." + ext, text);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
std::vector<std::vector<Tri>> cubeHalves() {
|
|
496
|
+
const std::vector<Tri> c = cubeTris(0, 0, 0);
|
|
497
|
+
return {{c.begin(), c.begin() + 6}, {c.begin() + 6, c.end()}};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
} // namespace
|
|
501
|
+
|
|
502
|
+
// Every mesh format welds exactly-equal vertices, as AMF and STL do, so a
|
|
503
|
+
// closed solid written as a triangle soup imports closed. OpenSCAD
|
|
504
|
+
// 2026.02.01 imports such an OFF, OBJ or 3MF as a solid that takes part in
|
|
505
|
+
// CSG; X3D and VRML (which it cannot import) follow AMF. Without the weld
|
|
506
|
+
// each was an open surface with 36 boundary edges.
|
|
507
|
+
TEST(ImportModuleContext, EveryMeshFormatWeldsATriangleSoupClosed) {
|
|
508
|
+
for (const char* ext : {"off", "obj", "3mf", "x3d", "wrl"}) {
|
|
509
|
+
const Imported r = importSoup(ext, {cubeTris(0, 0, 0)});
|
|
510
|
+
EXPECT_NEAR(r.volume, 1.0, 1e-9) << ext;
|
|
511
|
+
EXPECT_FALSE(anyContains(r.messages, "not a closed solid")) << ext << ::testing::PrintToString(r.messages);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Where the weld stops. OFF is one mesh, and OBJ welds across its `o`
|
|
516
|
+
// groups (checked, 2026.02.01: two cube halves in separate `o` groups, each
|
|
517
|
+
// with its own vertex copies, import as one closed cube) -- so the halves
|
|
518
|
+
// close. 3MF welds each object on its own (OpenSCAD reports both halves
|
|
519
|
+
// not closed), and X3D/VRML each Shape, as AMF does -- so they stay open.
|
|
520
|
+
TEST(ImportModuleContext, MeshWeldObjectBoundaryRule) {
|
|
521
|
+
for (const char* ext : {"off", "obj"}) {
|
|
522
|
+
const Imported r = importSoup(ext, cubeHalves());
|
|
523
|
+
EXPECT_NEAR(r.volume, 1.0, 1e-9) << ext;
|
|
524
|
+
EXPECT_FALSE(anyContains(r.messages, "not a closed solid")) << ext << ::testing::PrintToString(r.messages);
|
|
525
|
+
}
|
|
526
|
+
for (const char* ext : {"3mf", "x3d", "wrl"})
|
|
527
|
+
EXPECT_TRUE(anyContains(importSoup(ext, cubeHalves()).messages, "not a closed solid")) << ext;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// A cube whose eight vertices are listed twice, plus a triangle naming a
|
|
531
|
+
// vertex and its copy: welded, that triangle has two equal corners and
|
|
532
|
+
// drops out, leaving the closed cube (as OpenSCAD has it).
|
|
533
|
+
TEST(ImportModuleContext, OffDuplicatedVerticesAndATriangleDegenerateAfterWelding) {
|
|
534
|
+
const Imported r = importText("dup.off",
|
|
535
|
+
"OFF\n16 13 0\n0 0 0\n1 0 0\n1 1 0\n0 1 0\n0 0 1\n1 0 1\n1 1 1\n0 1 1\n"
|
|
536
|
+
"0 0 0\n1 0 0\n1 1 0\n0 1 0\n0 0 1\n1 0 1\n1 1 1\n0 1 1\n"
|
|
537
|
+
"3 0 11 2\n3 8 2 1\n3 4 13 6\n3 12 6 7\n3 0 9 5\n3 8 5 4\n"
|
|
538
|
+
"3 1 10 6\n3 9 6 5\n3 2 11 7\n3 10 7 6\n3 3 8 4\n3 11 4 7\n3 0 8 1\n");
|
|
539
|
+
EXPECT_NEAR(r.volume, 1.0, 1e-9);
|
|
540
|
+
EXPECT_FALSE(anyContains(r.messages, "not a closed solid")) << ::testing::PrintToString(r.messages);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// A face index past the vertex list is an error, now that the weld reads
|
|
544
|
+
// every index (it was handed to Manifold unchecked before).
|
|
545
|
+
TEST(ImportModuleContext, ObjFaceIndexPastItsVerticesErrors) {
|
|
546
|
+
const auto path = tempPath("bad.obj");
|
|
547
|
+
std::ofstream(path) << "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 9\n";
|
|
548
|
+
const std::string err = importFailure("import(\"" + path.generic_string() + "\");");
|
|
549
|
+
EXPECT_EQ(err.rfind("ERROR: ", 0), 0u) << err;
|
|
550
|
+
EXPECT_NE(err.find("face index 8 is past its 3 vertices"), std::string::npos) << err;
|
|
551
|
+
std::filesystem::remove(path);
|
|
552
|
+
}
|
|
553
|
+
|
|
363
554
|
TEST(ImportModuleContext, UnsupportedExtensionErrors) {
|
|
364
555
|
const auto path = tempPath("unsupported.xyz");
|
|
365
556
|
std::ofstream(path) << "x";
|
|
File without changes
|
{openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/.github/workflows/wheels.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/examples/eval_perf_benchmark.cpp
RENAMED
|
File without changes
|
{openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/examples/manifold_cache_reuse.cpp
RENAMED
|
File without changes
|
{openscad_cpp_evaluator-1.45.2 → openscad_cpp_evaluator-1.45.3}/examples/minimal_debugger.cpp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|