openscad-cpp-evaluator 1.9.0__tar.gz → 1.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/CLAUDE.md +17 -6
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/bindings/module.cpp +14 -3
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/colored_body.hpp +4 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/export.hpp +32 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/python/openscad_cpp_evaluator/__init__.py +12 -2
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/colored_body.cpp +7 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/export.cpp +118 -1
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_export.cpp +182 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_python_bindings.py +40 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/README.md +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/format_closure.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/utf8.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_feature_detection.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_include_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/uv.lock +0 -0
|
@@ -916,8 +916,8 @@ grep for `ponytail:`.
|
|
|
916
916
|
— skip it and the mesh comes out non-manifold at the seam). Self-contained 2D geometry math here
|
|
917
917
|
(`dot2`/`len2`/`norm2`, the parabola-canonical-frame rotation in `discretizeArc`) exists nowhere
|
|
918
918
|
else in this codebase.
|
|
919
|
-
- `include/openscad_cpp_evaluator/export.hpp`, `src/export.cpp` — the writers for all
|
|
920
|
-
formats `exportExtensions()` lists: 3MF, AMF, STL, OBJ, OFF, PLY, VRML, X3D (3MF via
|
|
919
|
+
- `include/openscad_cpp_evaluator/export.hpp`, `src/export.cpp` — the writers for all nine
|
|
920
|
+
formats `exportExtensions()` lists: 3MF, AMF, STL, OBJ, OFF, PLY, SVG, VRML, X3D (3MF via
|
|
921
921
|
`zip_stored.hpp`'s `writeDeflateZip`). **`exportExtensions()` is the one source of truth for
|
|
922
922
|
which formats exist** — the Python facade and BelfrySCAD's dialog/CLI all ask it rather than
|
|
923
923
|
restating the list, because three hand-written copies had already drifted far enough that `.off`
|
|
@@ -927,11 +927,22 @@ grep for `ponytail:`.
|
|
|
927
927
|
than the face, so an object whose triangles are not all one colour is written as one volume per
|
|
928
928
|
colour — per-triangle `<color>` is legal in the spec but poorly supported by the slicers that are
|
|
929
929
|
the audience for it.
|
|
930
|
+
**`writeSvg` is the one 2D writer**, and the only one that reads `ColoredBody::section` rather
|
|
931
|
+
than `.body`: SVG 1.1, millimetres at 1:1, every contour of a body (holes included) a
|
|
932
|
+
`M .. L .. z` subpath of that body's single `<path>`. Model space is Y-up and SVG's is Y-down, so
|
|
933
|
+
the whole point transform is a Y negation; the page is the bounding box padded by **half the
|
|
934
|
+
stroke width** and rounded outward to whole millimetres, which is why the margin is not a
|
|
935
|
+
constant and why `stroke=false` shrinks the document. Both rules are OpenSCAD's, read from its
|
|
936
|
+
`export_svg.cc` — `ExportSvg.MatchesRealOpenscadByteForByte` pins the output against the real
|
|
937
|
+
binary's, vertex order and 6-vertex line wrap included. A 3D body in the list throws
|
|
938
|
+
("Current top level object is not a 2D object"), as OpenSCAD refuses too: there is no projection
|
|
939
|
+
to fall back on. `ExportOptions::svg` carries OpenSCAD's five `-O export-svg/...` settings.
|
|
930
940
|
`colored_body.hpp`/`.cpp`'s `toRenderableBodies()` (thin-extrudes any top-level 2D-only result to
|
|
931
|
-
a
|
|
932
|
-
writers — none of them look at `ColoredBody::section` at all, only `.body`.
|
|
933
|
-
|
|
934
|
-
|
|
941
|
+
a 1-unit-tall `flatPreview` Manifold) must run on a body list before handing it to any of the
|
|
942
|
+
MESH writers — none of them look at `ColoredBody::section` at all, only `.body`. Not before
|
|
943
|
+
`writeSvg`, which needs the sections that step throws away, and which says so rather than writing
|
|
944
|
+
the slab's outline. `tools/cli/cli_lib.cpp` is the only current caller; a future GUI/renderer
|
|
945
|
+
caller needs the same call before passing results to its own mesh consumer.
|
|
935
946
|
- `include/openscad_cpp_evaluator/function_builtins.hpp`, `src/builtins/function_builtins.cpp` —
|
|
936
947
|
the math/string/list/type-check builtin *function* dispatch (`isBuiltinFunctionName`/
|
|
937
948
|
`evalBuiltinFunction`), a completely separate namespace/table from `dispatch.hpp`'s builtin
|
|
@@ -357,7 +357,8 @@ struct Geometry {
|
|
|
357
357
|
// exportModel, with the path/format/warnings marshalling. Releases the GIL:
|
|
358
358
|
// a large export is seconds of Manifold work with no Python involved.
|
|
359
359
|
nb::list exportModelPy(const std::string& path, const Geometry& geom, const std::string& format, bool asciiStl,
|
|
360
|
-
bool stripSlivers, bool splitComponents
|
|
360
|
+
bool stripSlivers, bool splitComponents, bool svgFill, const std::string& svgFillColor,
|
|
361
|
+
bool svgStroke, const std::string& svgStrokeColor, double svgStrokeWidth) {
|
|
361
362
|
std::vector<std::string> warnings;
|
|
362
363
|
{
|
|
363
364
|
nb::gil_scoped_release rel;
|
|
@@ -366,6 +367,11 @@ nb::list exportModelPy(const std::string& path, const Geometry& geom, const std:
|
|
|
366
367
|
opts.asciiStl = asciiStl;
|
|
367
368
|
opts.stripSlivers = stripSlivers;
|
|
368
369
|
opts.splitComponents = splitComponents;
|
|
370
|
+
opts.svg.fill = svgFill;
|
|
371
|
+
opts.svg.fillColor = svgFillColor;
|
|
372
|
+
opts.svg.stroke = svgStroke;
|
|
373
|
+
opts.svg.strokeColor = svgStrokeColor;
|
|
374
|
+
opts.svg.strokeWidth = svgStrokeWidth;
|
|
369
375
|
warnings = oscadeval::exportModel(path, geom.bodies, opts);
|
|
370
376
|
}
|
|
371
377
|
nb::list out;
|
|
@@ -793,14 +799,19 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
|
|
|
793
799
|
|
|
794
800
|
m.def("export_model", &exportModelPy, nb::arg("path"), nb::arg("geometry"), nb::arg("format") = std::string(),
|
|
795
801
|
nb::arg("ascii_stl") = false, nb::arg("strip_slivers") = true,
|
|
796
|
-
nb::arg("split_components") = false,
|
|
802
|
+
nb::arg("split_components") = false, nb::arg("svg_fill") = false,
|
|
803
|
+
nb::arg("svg_fill_color") = std::string("white"), nb::arg("svg_stroke") = true,
|
|
804
|
+
nb::arg("svg_stroke_color") = std::string("black"), nb::arg("svg_stroke_width") = 0.35,
|
|
797
805
|
"Write `geometry` to `path`, format taken from the extension unless `format` says otherwise. "
|
|
798
806
|
"Returns the warnings to surface (open shells, mesh problems, slivers removed) rather than "
|
|
799
807
|
"logging them. Raises RuntimeError when there is no geometry, the format is unknown, or the "
|
|
800
808
|
"file cannot be opened.\n\n"
|
|
801
809
|
"`split_components` gives every disconnected piece its own object in the multi-object formats "
|
|
802
810
|
"(3MF, AMF, OBJ, PLY, VRML, X3D). Off by default, matching OpenSCAD, which writes one object "
|
|
803
|
-
"per colour however many pieces it is in
|
|
811
|
+
"per colour however many pieces it is in.\n\n"
|
|
812
|
+
"The svg_* arguments are OpenSCAD's -O export-svg/... set and apply to .svg only. "
|
|
813
|
+
"svg_stroke/svg_stroke_width also pad the page, so they are not purely cosmetic. An .svg "
|
|
814
|
+
"export needs an all-2D model and raises otherwise, as OpenSCAD does.");
|
|
804
815
|
|
|
805
816
|
m.def("export_extensions", []() { return oscadeval::exportExtensions(); },
|
|
806
817
|
"The file extensions export_model understands, dot-prefixed. Read from the C++ writer table "
|
|
@@ -16,6 +16,10 @@ enum class BodyRole { Normal, Highlight, Background, ShowOnly };
|
|
|
16
16
|
// A single piece of evaluated geometry: exactly one of `body` (3D) or
|
|
17
17
|
// `section` (2D) is set, never both -- mirrors the Python reference's
|
|
18
18
|
// ColoredBody (body: Optional[Manifold] XOR section: Optional[CrossSection]).
|
|
19
|
+
// ONE exception, and it is deliberate: toRenderableBodies() leaves the
|
|
20
|
+
// `section` in place on the `flatPreview` body it extrudes, so a 2D writer
|
|
21
|
+
// downstream still has the contours. `flatPreview` is the flag that says
|
|
22
|
+
// so; nothing else sets both.
|
|
19
23
|
// `role`/`triColors` are later-phase fields (modifier tagging -- Phase 3;
|
|
20
24
|
// multi-color CSG merges -- Phase 3/8) included now so the struct shape
|
|
21
25
|
// doesn't need reworking when those land.
|
|
@@ -127,6 +127,36 @@ void writeOff(const std::string& path, const std::vector<ColoredBody>& bodies);
|
|
|
127
127
|
// no geometry to export.
|
|
128
128
|
void writeThreeMf(const std::string& path, const std::vector<ExportObject>& objects);
|
|
129
129
|
|
|
130
|
+
// -- 2D ------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
// Options for the SVG writer, named and defaulted after OpenSCAD's own
|
|
133
|
+
// `-O export-svg/...` set. `stroke`/`strokeWidth` are not purely cosmetic:
|
|
134
|
+
// half the stroke width pads the page, so turning the stroke off shrinks
|
|
135
|
+
// the document.
|
|
136
|
+
struct ExportSvgOptions {
|
|
137
|
+
bool fill = false;
|
|
138
|
+
std::string fillColor = "white";
|
|
139
|
+
bool stroke = true;
|
|
140
|
+
std::string strokeColor = "black";
|
|
141
|
+
double strokeWidth = 0.35;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// SVG 1.1 of a 2D-only model, in millimetres at 1:1 -- so a print of it
|
|
145
|
+
// measures what the script says. Every contour of a body, holes included,
|
|
146
|
+
// becomes a `M .. L .. z` subpath of that body's single `<path>`.
|
|
147
|
+
//
|
|
148
|
+
// Model space is Y-up and SVG's is Y-down, so the whole point transform is
|
|
149
|
+
// a Y negation. The page is the bounding box padded by half the stroke
|
|
150
|
+
// width and rounded OUTWARD to whole millimetres, which is why the margin
|
|
151
|
+
// is not a constant (0 to just under 1mm per side, depending where the
|
|
152
|
+
// bounds fall). Both rules are OpenSCAD's, read from its export_svg.cc and
|
|
153
|
+
// checked against its binary.
|
|
154
|
+
//
|
|
155
|
+
// Throws std::runtime_error if any top-level body is 3D -- OpenSCAD's rule,
|
|
156
|
+
// and the only honest answer, since there is no projection to fall back on.
|
|
157
|
+
void writeSvg(const std::string& path, const std::vector<ColoredBody>& bodies,
|
|
158
|
+
const ExportSvgOptions& opts = {});
|
|
159
|
+
|
|
130
160
|
// -- one entry point ------------------------------------------------------
|
|
131
161
|
|
|
132
162
|
struct ExportOptions {
|
|
@@ -144,6 +174,8 @@ struct ExportOptions {
|
|
|
144
174
|
// pieces it is in; see that function's own comment for why the default
|
|
145
175
|
// moved here.
|
|
146
176
|
bool splitComponents = false;
|
|
177
|
+
// .svg only.
|
|
178
|
+
ExportSvgOptions svg;
|
|
147
179
|
};
|
|
148
180
|
|
|
149
181
|
// Writes `bodies` to `path`, choosing the writer from the extension (or
|
|
@@ -292,7 +292,10 @@ def strip_slivers(verts, tris):
|
|
|
292
292
|
|
|
293
293
|
|
|
294
294
|
def export_model(path: str, geometry, format: str = "", ascii_stl: bool = False,
|
|
295
|
-
strip_slivers: bool = True, split_components: bool = False
|
|
295
|
+
strip_slivers: bool = True, split_components: bool = False,
|
|
296
|
+
svg_fill: bool = False, svg_fill_color: str = "white",
|
|
297
|
+
svg_stroke: bool = True, svg_stroke_color: str = "black",
|
|
298
|
+
svg_stroke_width: float = 0.35) -> list:
|
|
296
299
|
"""Write `geometry` to `path`. Returns the warnings to surface.
|
|
297
300
|
|
|
298
301
|
`geometry` is the handle an Evaluator stashes on itself as
|
|
@@ -312,10 +315,17 @@ def export_model(path: str, geometry, format: str = "", ascii_stl: bool = False,
|
|
|
312
315
|
|
|
313
316
|
`split_components` gives every disconnected piece its own object in the
|
|
314
317
|
multi-object formats. Off by default, which is what OpenSCAD writes.
|
|
318
|
+
|
|
319
|
+
The `svg_*` arguments are OpenSCAD's `-O export-svg/...` set and apply
|
|
320
|
+
to `.svg` only, which writes a 2D model at 1:1 in millimetres.
|
|
321
|
+
`svg_stroke`/`svg_stroke_width` also pad the page, so they are not
|
|
322
|
+
purely cosmetic. An `.svg` export of anything 3D raises, as OpenSCAD's
|
|
323
|
+
does -- there is no projection to fall back on.
|
|
315
324
|
"""
|
|
316
325
|
try:
|
|
317
326
|
return list(_ext.export_model(path, geometry, format, ascii_stl, strip_slivers,
|
|
318
|
-
split_components
|
|
327
|
+
split_components, svg_fill, svg_fill_color,
|
|
328
|
+
svg_stroke, svg_stroke_color, svg_stroke_width))
|
|
319
329
|
except Exception as e:
|
|
320
330
|
raise EvalError(str(e)) from e
|
|
321
331
|
|
|
@@ -43,6 +43,13 @@ std::vector<ColoredBody> toRenderableBodies(const std::vector<ColoredBody>& bodi
|
|
|
43
43
|
flat.body = flat.body->Transform(cb.sectionXform);
|
|
44
44
|
flat.color = cb.color;
|
|
45
45
|
flat.flatPreview = true;
|
|
46
|
+
// Carried, not consumed: the 2D writers (writeSvg) need the
|
|
47
|
+
// contours, and every caller that exports goes through this
|
|
48
|
+
// conversion first -- the Python binding hands ONE body list to
|
|
49
|
+
// both the renderer and export. Dropping it here made an SVG
|
|
50
|
+
// export of any 2D script impossible from Python.
|
|
51
|
+
flat.section = cb.section;
|
|
52
|
+
flat.sectionXform = cb.sectionXform;
|
|
46
53
|
flat.role = cb.role;
|
|
47
54
|
out.push_back(std::move(flat));
|
|
48
55
|
} else {
|
|
@@ -999,8 +999,117 @@ void writeOffMesh(const std::string& path, const manifold::MeshGL& mesh) {
|
|
|
999
999
|
|
|
1000
1000
|
} // namespace
|
|
1001
1001
|
|
|
1002
|
+
namespace {
|
|
1003
|
+
|
|
1004
|
+
// The contours of one 2D body, projected to XY.
|
|
1005
|
+
//
|
|
1006
|
+
// A section can carry a transform a CrossSection itself cannot hold -- a Z
|
|
1007
|
+
// offset, a rotation out of the plane (ColoredBody::sectionXform). Applying
|
|
1008
|
+
// it and keeping x/y is the top view, which is the only thing a flat page
|
|
1009
|
+
// can show; for the overwhelmingly common case (no such transform) it is
|
|
1010
|
+
// the identity and the points pass through untouched.
|
|
1011
|
+
manifold::Polygons sectionContours(const ColoredBody& cb) {
|
|
1012
|
+
manifold::Polygons polys = cb.section->ToPolygons();
|
|
1013
|
+
const manifold::mat3x4& m = cb.sectionXform;
|
|
1014
|
+
for (manifold::SimplePolygon& contour : polys) {
|
|
1015
|
+
for (manifold::vec2& p : contour) {
|
|
1016
|
+
const double x = m[0].x * p.x + m[1].x * p.y + m[3].x;
|
|
1017
|
+
const double y = m[0].y * p.x + m[1].y * p.y + m[3].y;
|
|
1018
|
+
p = manifold::vec2(x, y);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return polys;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
} // namespace
|
|
1025
|
+
|
|
1026
|
+
void writeSvg(const std::string& path, const std::vector<ColoredBody>& bodies, const ExportSvgOptions& opts) {
|
|
1027
|
+
// OpenSCAD's own wording, and its rule: 2D export needs an all-2D top
|
|
1028
|
+
// level. There is no sane projection to fall back on -- a silhouette
|
|
1029
|
+
// would be a different model from the one the script describes -- so
|
|
1030
|
+
// refusing is the honest answer.
|
|
1031
|
+
const char* kNot2d = "Current top level object is not a 2D object";
|
|
1032
|
+
|
|
1033
|
+
std::vector<manifold::Polygons> perBody;
|
|
1034
|
+
double minx = 0, miny = 0, maxx = 0, maxy = 0;
|
|
1035
|
+
bool any = false;
|
|
1036
|
+
for (const ColoredBody& cb : bodies) {
|
|
1037
|
+
if (!isExportable(cb)) continue;
|
|
1038
|
+
// The section decides, not the Manifold: a body that has been
|
|
1039
|
+
// through toRenderableBodies() carries BOTH -- a 1-unit slab
|
|
1040
|
+
// extruded so a 2D script has something to show, and the contours
|
|
1041
|
+
// it came from. Those contours are the real geometry, and the
|
|
1042
|
+
// binding hands export that same converted list, so reading `body`
|
|
1043
|
+
// first would make SVG unreachable for every 2D script.
|
|
1044
|
+
if (!cb.section) {
|
|
1045
|
+
if (cb.isDisplayOnly()) throw std::runtime_error(kNot2d);
|
|
1046
|
+
if (cb.body && !cb.body->IsEmpty()) throw std::runtime_error(kNot2d);
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
manifold::Polygons polys = sectionContours(cb);
|
|
1050
|
+
for (const manifold::SimplePolygon& contour : polys) {
|
|
1051
|
+
for (const manifold::vec2& p : contour) {
|
|
1052
|
+
if (!any) {
|
|
1053
|
+
minx = maxx = p.x;
|
|
1054
|
+
miny = maxy = p.y;
|
|
1055
|
+
any = true;
|
|
1056
|
+
} else {
|
|
1057
|
+
minx = std::min(minx, p.x);
|
|
1058
|
+
maxx = std::max(maxx, p.x);
|
|
1059
|
+
miny = std::min(miny, p.y);
|
|
1060
|
+
maxy = std::max(maxy, p.y);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
perBody.push_back(std::move(polys));
|
|
1065
|
+
}
|
|
1066
|
+
if (!any) throw std::runtime_error("No geometry to export");
|
|
1067
|
+
|
|
1068
|
+
// Page = bounding box, padded by half the stroke (the stroke straddles
|
|
1069
|
+
// the contour, so half of it lies outside), rounded outward to whole
|
|
1070
|
+
// millimetres. Note the Y terms use -maxy/-miny: the negation below
|
|
1071
|
+
// turns the TOP of the model into the most negative coordinate.
|
|
1072
|
+
const double pad = opts.stroke ? opts.strokeWidth / 2.0 : 0.0;
|
|
1073
|
+
const long left = static_cast<long>(std::floor(minx - pad));
|
|
1074
|
+
const long top = static_cast<long>(std::floor(-maxy - pad));
|
|
1075
|
+
const long right = static_cast<long>(std::ceil(maxx + pad));
|
|
1076
|
+
const long bottom = static_cast<long>(std::ceil(-miny + pad));
|
|
1077
|
+
const long width = right - left;
|
|
1078
|
+
const long height = bottom - top;
|
|
1079
|
+
|
|
1080
|
+
std::ofstream out(path);
|
|
1081
|
+
if (!out) throw std::runtime_error("Could not open '" + path + "' for writing");
|
|
1082
|
+
|
|
1083
|
+
const std::string stroke = opts.stroke ? opts.strokeColor : "none";
|
|
1084
|
+
const std::string fill = opts.fill ? opts.fillColor : "none";
|
|
1085
|
+
|
|
1086
|
+
out << "<?xml version=\"1.0\" standalone=\"no\"?>\n"
|
|
1087
|
+
<< "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
|
|
1088
|
+
"\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
|
|
1089
|
+
<< "<svg width=\"" << width << "mm\" height=\"" << height << "mm\" viewBox=\"" << left << " " << top
|
|
1090
|
+
<< " " << width << " " << height << "\" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n"
|
|
1091
|
+
<< "<title>OpenSCAD Model</title>\n";
|
|
1092
|
+
|
|
1093
|
+
for (const manifold::Polygons& polys : perBody) {
|
|
1094
|
+
out << "<path d=\"\n";
|
|
1095
|
+
for (const manifold::SimplePolygon& contour : polys) {
|
|
1096
|
+
if (contour.empty()) continue;
|
|
1097
|
+
out << "M " << contour[0].x << "," << -contour[0].y;
|
|
1098
|
+
for (size_t i = 1; i < contour.size(); ++i) {
|
|
1099
|
+
out << " L " << contour[i].x << "," << -contour[i].y;
|
|
1100
|
+
if ((i % 6) == 5) out << "\n";
|
|
1101
|
+
}
|
|
1102
|
+
out << " z\n";
|
|
1103
|
+
}
|
|
1104
|
+
out << "\" stroke=\"" << stroke << "\" fill=\"" << fill << "\" stroke-width=\"" << opts.strokeWidth
|
|
1105
|
+
<< "\"/>\n";
|
|
1106
|
+
}
|
|
1107
|
+
out << "</svg>\n";
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1002
1110
|
const std::vector<std::string>& exportExtensions() {
|
|
1003
|
-
static const std::vector<std::string> exts = {".3mf", ".amf", ".stl", ".obj", ".off",
|
|
1111
|
+
static const std::vector<std::string> exts = {".3mf", ".amf", ".stl", ".obj", ".off",
|
|
1112
|
+
".ply", ".svg", ".wrl", ".x3d"};
|
|
1004
1113
|
return exts;
|
|
1005
1114
|
}
|
|
1006
1115
|
|
|
@@ -1021,6 +1130,14 @@ std::vector<std::string> exportModel(const std::string& path, const std::vector<
|
|
|
1021
1130
|
}
|
|
1022
1131
|
};
|
|
1023
1132
|
|
|
1133
|
+
if (ext == ".svg") {
|
|
1134
|
+
// 2D, and nothing the mesh pipeline below does applies: no merge,
|
|
1135
|
+
// no sliver strip, no manifoldness check. Contours are what they
|
|
1136
|
+
// are.
|
|
1137
|
+
writeSvg(path, bodies, opts.svg);
|
|
1138
|
+
return warnings;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1024
1141
|
if (isMultiObject(ext)) {
|
|
1025
1142
|
// These keep the parts as separate objects, so each is checked on
|
|
1026
1143
|
// its own -- that is what the file contains.
|
|
@@ -258,3 +258,185 @@ TEST(ExportAmf, AMultiColouredObjectBecomesOneVolumePerColour) {
|
|
|
258
258
|
EXPECT_EQ(countOf(xml, "<material id="), 2u);
|
|
259
259
|
std::remove(path.c_str());
|
|
260
260
|
}
|
|
261
|
+
|
|
262
|
+
// -- SVG (2D) --------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
namespace {
|
|
265
|
+
|
|
266
|
+
std::string readText(const std::string& path) {
|
|
267
|
+
std::ifstream in(path);
|
|
268
|
+
return std::string((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Write `code` as SVG and return the file's text, deleting the file.
|
|
272
|
+
std::string svgOf(const std::string& code, const std::string& name, const ExportSvgOptions& opts = {}) {
|
|
273
|
+
const std::string path = tempPath(name).string();
|
|
274
|
+
writeSvg(path, evalToBodies(code), opts);
|
|
275
|
+
const std::string text = readText(path);
|
|
276
|
+
std::remove(path.c_str());
|
|
277
|
+
return text;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
std::string firstLineContaining(const std::string& text, const std::string& needle) {
|
|
281
|
+
const size_t at = text.find(needle);
|
|
282
|
+
if (at == std::string::npos) return "";
|
|
283
|
+
const size_t start = text.rfind('\n', at);
|
|
284
|
+
const size_t end = text.find('\n', at);
|
|
285
|
+
return text.substr(start == std::string::npos ? 0 : start + 1,
|
|
286
|
+
(end == std::string::npos ? text.size() : end) - (start == std::string::npos ? 0 : start + 1));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
} // namespace
|
|
290
|
+
|
|
291
|
+
// The page rule is NOT a fixed 1mm margin -- it is the bounding box padded
|
|
292
|
+
// by half the stroke width, then rounded outward to whole millimetres. The
|
|
293
|
+
// three cases below are the ones that tell those two rules apart, and each
|
|
294
|
+
// header is what real OpenSCAD 2026.02.01 wrote for the same model.
|
|
295
|
+
TEST(ExportSvg, PageIsStrokePaddedBoundsRoundedOutward) {
|
|
296
|
+
const std::string text = svgOf("square([70.4, 25.3]);", "page_default.svg");
|
|
297
|
+
EXPECT_EQ(firstLineContaining(text, "<svg "),
|
|
298
|
+
"<svg width=\"72mm\" height=\"27mm\" viewBox=\"-1 -26 72 27\" "
|
|
299
|
+
"xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
TEST(ExportSvg, NoStrokeMeansNoStrokePadding) {
|
|
303
|
+
ExportSvgOptions opts;
|
|
304
|
+
opts.stroke = false;
|
|
305
|
+
const std::string text = svgOf("square([70.4, 25.3]);", "page_nostroke.svg", opts);
|
|
306
|
+
EXPECT_EQ(firstLineContaining(text, "<svg "),
|
|
307
|
+
"<svg width=\"71mm\" height=\"26mm\" viewBox=\"0 -26 71 26\" "
|
|
308
|
+
"xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">");
|
|
309
|
+
EXPECT_NE(text.find("stroke=\"none\""), std::string::npos);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
TEST(ExportSvg, WiderStrokeGrowsThePage) {
|
|
313
|
+
ExportSvgOptions opts;
|
|
314
|
+
opts.strokeWidth = 4.0;
|
|
315
|
+
const std::string text = svgOf("square([70.4, 25.3]);", "page_wide.svg", opts);
|
|
316
|
+
EXPECT_EQ(firstLineContaining(text, "<svg "),
|
|
317
|
+
"<svg width=\"75mm\" height=\"30mm\" viewBox=\"-2 -28 75 30\" "
|
|
318
|
+
"xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
TEST(ExportSvg, NegatesY) {
|
|
322
|
+
// Model Y 10..15 must come out as SVG y -15..-10: SVG's Y grows
|
|
323
|
+
// downward. Getting this backwards mirrors the drawing, and on a
|
|
324
|
+
// symmetric model it is invisible -- hence an asymmetric one.
|
|
325
|
+
const std::string text = svgOf("translate([0,10]) square([5,5]);", "negate_y.svg");
|
|
326
|
+
EXPECT_NE(text.find(",-15"), std::string::npos);
|
|
327
|
+
EXPECT_NE(text.find(",-10"), std::string::npos);
|
|
328
|
+
EXPECT_EQ(text.find(",15"), std::string::npos);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
TEST(ExportSvg, HoleIsAnotherSubpathOfTheSamePath) {
|
|
332
|
+
const std::string text =
|
|
333
|
+
svgOf("difference() { square([40,25]); translate([20,12]) circle(5, $fn=16); }", "hole.svg");
|
|
334
|
+
EXPECT_EQ(countOf(text, "<path "), 1u); // one body, one <path>
|
|
335
|
+
EXPECT_EQ(countOf(text, "M "), 2u); // outline + hole
|
|
336
|
+
EXPECT_EQ(countOf(text, " z"), 2u);
|
|
337
|
+
// fill="none" is why the hole needs no winding rule to show through.
|
|
338
|
+
EXPECT_NE(text.find("fill=\"none\""), std::string::npos);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
TEST(ExportSvg, DisjointShapesShareOneBodyAndOnePath) {
|
|
342
|
+
const std::string text = svgOf("square(10); translate([20,0]) square(10);", "disjoint.svg");
|
|
343
|
+
EXPECT_EQ(countOf(text, "M "), 2u);
|
|
344
|
+
// 0..30 in x, padded by 0.175 and rounded out: -1 .. 31.
|
|
345
|
+
EXPECT_NE(text.find("<svg width=\"32mm\" height=\"12mm\" viewBox=\"-1 -11 32 12\""), std::string::npos);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
TEST(ExportSvg, StrokeAndFillOptionsReachTheFile) {
|
|
349
|
+
ExportSvgOptions opts;
|
|
350
|
+
opts.fill = true;
|
|
351
|
+
opts.fillColor = "#ffcc00";
|
|
352
|
+
opts.strokeColor = "red";
|
|
353
|
+
opts.strokeWidth = 0.5;
|
|
354
|
+
const std::string text = svgOf("square(10);", "styled.svg", opts);
|
|
355
|
+
EXPECT_NE(text.find("stroke=\"red\""), std::string::npos);
|
|
356
|
+
EXPECT_NE(text.find("fill=\"#ffcc00\""), std::string::npos);
|
|
357
|
+
EXPECT_NE(text.find("stroke-width=\"0.5\""), std::string::npos);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
TEST(ExportSvg, SectionTransformIsApplied) {
|
|
361
|
+
// A transform a CrossSection cannot hold -- here a rotation out of the
|
|
362
|
+
// XY plane -- lives in ColoredBody::sectionXform instead. Ignoring it
|
|
363
|
+
// drew the unrotated shape. Tilting a 10mm square 45 degrees about X
|
|
364
|
+
// foreshortens it to 10*cos(45) = 7.07107 on the page.
|
|
365
|
+
const std::string text = svgOf("rotate([45,0,0]) square([10,10]);", "xform.svg");
|
|
366
|
+
EXPECT_NE(text.find("-7.07107"), std::string::npos);
|
|
367
|
+
EXPECT_NE(text.find("<svg width=\"12mm\" height=\"9mm\" viewBox=\"-1 -8 12 9\""), std::string::npos);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
TEST(ExportSvg, InPlaneTranslationLandsWhereTheModelIs) {
|
|
371
|
+
const std::string text = svgOf("translate([3,4]) square([5,5]);", "moved.svg");
|
|
372
|
+
EXPECT_NE(text.find("M 3,-4"), std::string::npos);
|
|
373
|
+
EXPECT_NE(text.find("L 8,-9"), std::string::npos);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
TEST(ExportSvg, RefusesA3dModel) {
|
|
377
|
+
std::vector<ColoredBody> bodies = evalToBodies("cube(10);");
|
|
378
|
+
EXPECT_THROW(writeSvg(tempPath("solid.svg").string(), bodies), std::runtime_error);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
TEST(ExportSvg, RefusesMixed2dAnd3d) {
|
|
382
|
+
std::vector<ColoredBody> bodies = evalToBodies("cube(10); translate([20,0]) square(5);");
|
|
383
|
+
EXPECT_THROW(writeSvg(tempPath("mixed.svg").string(), bodies), std::runtime_error);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
TEST(ExportSvg, WorksOnRenderableBodiesToo) {
|
|
387
|
+
// The Python binding runs toRenderableBodies() once and hands the SAME
|
|
388
|
+
// list to the renderer and to export, so a 2D script reaches writeSvg
|
|
389
|
+
// as a 1-unit slab that still carries its section. The contours are
|
|
390
|
+
// what gets written -- the slab's own outline would be the same shape
|
|
391
|
+
// but is not the point; the section is the geometry.
|
|
392
|
+
std::vector<ColoredBody> renderable = toRenderableBodies(evalToBodies("square([70.4, 25.3]);"));
|
|
393
|
+
ASSERT_TRUE(renderable[0].flatPreview);
|
|
394
|
+
ASSERT_TRUE(renderable[0].section.has_value());
|
|
395
|
+
const std::string path = tempPath("preview.svg").string();
|
|
396
|
+
writeSvg(path, renderable);
|
|
397
|
+
const std::string text = readText(path);
|
|
398
|
+
std::remove(path.c_str());
|
|
399
|
+
EXPECT_NE(text.find("<svg width=\"72mm\" height=\"27mm\" viewBox=\"-1 -26 72 27\""), std::string::npos);
|
|
400
|
+
EXPECT_EQ(countOf(text, "M "), 1u);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
TEST(ExportSvg, NoGeometryThrows) {
|
|
404
|
+
std::vector<ColoredBody> empty;
|
|
405
|
+
EXPECT_THROW(writeSvg(tempPath("empty.svg").string(), empty), std::runtime_error);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
TEST(ExportSvg, ReachableThroughExportModelAndListedAsAnExtension) {
|
|
409
|
+
const std::vector<std::string>& exts = exportExtensions();
|
|
410
|
+
EXPECT_NE(std::find(exts.begin(), exts.end(), ".svg"), exts.end());
|
|
411
|
+
|
|
412
|
+
const std::string path = tempPath("via_export_model.svg").string();
|
|
413
|
+
const std::vector<std::string> warnings = exportModel(path, evalToBodies("circle(5, $fn=8);"), ExportOptions{});
|
|
414
|
+
EXPECT_TRUE(warnings.empty()); // none of the mesh checks apply to contours
|
|
415
|
+
const std::string text = readText(path);
|
|
416
|
+
EXPECT_NE(text.find("<svg "), std::string::npos);
|
|
417
|
+
EXPECT_EQ(countOf(text, "M "), 1u);
|
|
418
|
+
std::remove(path.c_str());
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Byte-for-byte against real OpenSCAD 2026.02.01's own `-o out.svg` for the
|
|
422
|
+
// same script -- header, vertex order, the 6-vertex line wrap and the "-0"
|
|
423
|
+
// that a negated zero prints as. Contour ORDER (outline before hole) and
|
|
424
|
+
// winding come from Manifold rather than CGAL, so agreeing here is worth
|
|
425
|
+
// pinning: it is the part that could drift without anything else noticing.
|
|
426
|
+
TEST(ExportSvg, MatchesRealOpenscadByteForByte) {
|
|
427
|
+
const std::string expected =
|
|
428
|
+
R"SVG(<?xml version="1.0" standalone="no"?>
|
|
429
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
430
|
+
<svg width="42mm" height="27mm" viewBox="-1 -26 42 27" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
|
431
|
+
<title>OpenSCAD Model</title>
|
|
432
|
+
<path d="
|
|
433
|
+
M 40,-25 L 0,-25 L 0,-0 L 40,-0 z
|
|
434
|
+
M 18.0866,-7.3806 L 16.4645,-8.46447 L 15.3806,-10.0866 L 15,-12 L 15.3806,-13.9134 L 16.4645,-15.5355
|
|
435
|
+
L 18.0866,-16.6194 L 20,-17 L 21.9134,-16.6194 L 23.5355,-15.5355 L 24.6194,-13.9134 L 25,-12
|
|
436
|
+
L 24.6194,-10.0866 L 23.5355,-8.46447 L 21.9134,-7.3806 L 20,-7 z
|
|
437
|
+
" stroke="black" fill="none" stroke-width="0.35"/>
|
|
438
|
+
</svg>
|
|
439
|
+
)SVG";
|
|
440
|
+
EXPECT_EQ(svgOf("difference() { square([40,25]); translate([20,12]) circle(5, $fn=16); }", "parity.svg"),
|
|
441
|
+
expected);
|
|
442
|
+
}
|
{openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/tests/test_python_bindings.py
RENAMED
|
@@ -661,6 +661,46 @@ def test_export_model_facade_accepts_split_components(tmp_path):
|
|
|
661
661
|
assert objects_in(tmp_path / "split.3mf", split_components=True) == 3
|
|
662
662
|
|
|
663
663
|
|
|
664
|
+
def test_export_model_writes_svg_and_takes_its_options(tmp_path):
|
|
665
|
+
""".svg through the facade, options included -- the same two-parameter-list
|
|
666
|
+
trap as test_export_model_facade_accepts_split_components."""
|
|
667
|
+
from openscad_cpp_evaluator import Evaluator, EvalError, export_extensions, export_model
|
|
668
|
+
|
|
669
|
+
assert ".svg" in export_extensions()
|
|
670
|
+
|
|
671
|
+
src = tmp_path / "flat.scad"
|
|
672
|
+
src.write_text("square([70.4, 25.3]);")
|
|
673
|
+
ev = Evaluator()
|
|
674
|
+
ev.evaluate(str(src), {})
|
|
675
|
+
|
|
676
|
+
out = tmp_path / "flat.svg"
|
|
677
|
+
assert export_model(str(out), ev.geometry) == []
|
|
678
|
+
text = out.read_text()
|
|
679
|
+
# Page = bounds + half the stroke, rounded outward; matches OpenSCAD's.
|
|
680
|
+
assert 'width="72mm" height="27mm" viewBox="-1 -26 72 27"' in text
|
|
681
|
+
assert 'stroke="black" fill="none" stroke-width="0.35"' in text
|
|
682
|
+
|
|
683
|
+
styled = tmp_path / "styled.svg"
|
|
684
|
+
export_model(str(styled), ev.geometry, svg_stroke=False, svg_fill=True, svg_fill_color="red")
|
|
685
|
+
styled_text = styled.read_text()
|
|
686
|
+
assert 'stroke="none" fill="red"' in styled_text
|
|
687
|
+
# No stroke, no stroke padding -- so the page shrinks.
|
|
688
|
+
assert 'width="71mm" height="26mm"' in styled_text
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def test_export_model_svg_refuses_a_3d_model(tmp_path):
|
|
692
|
+
"""OpenSCAD's rule, and ours: 2D export needs an all-2D top level."""
|
|
693
|
+
import pytest
|
|
694
|
+
from openscad_cpp_evaluator import Evaluator, EvalError, export_model
|
|
695
|
+
|
|
696
|
+
src = tmp_path / "solid.scad"
|
|
697
|
+
src.write_text("cube(10);")
|
|
698
|
+
ev = Evaluator()
|
|
699
|
+
ev.evaluate(str(src), {})
|
|
700
|
+
with pytest.raises(EvalError, match="not a 2D object"):
|
|
701
|
+
export_model(str(tmp_path / "solid.svg"), ev.geometry)
|
|
702
|
+
|
|
703
|
+
|
|
664
704
|
def test_strict_commas_reaches_the_parser(tmp_path):
|
|
665
705
|
"""Evaluator.evaluate(strict_commas=True) must survive both hand-written
|
|
666
706
|
parameter lists -- the facade's and the nanobind binding's. The last
|
|
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.9.0 → openscad_cpp_evaluator-1.10.0}/examples/eval_perf_benchmark.cpp
RENAMED
|
File without changes
|
{openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/examples/manifold_cache_reuse.cpp
RENAMED
|
File without changes
|
{openscad_cpp_evaluator-1.9.0 → openscad_cpp_evaluator-1.10.0}/examples/minimal_debugger.cpp
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
|