openscad-cpp-evaluator 1.20.5__tar.gz → 1.22.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.20.5 → openscad_cpp_evaluator-1.22.0}/.gitignore +2 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/CLAUDE.md +51 -1
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/bindings/module.cpp +31 -3
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/csg_node.hpp +7 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +15 -1
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/evaluator.hpp +83 -1
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/python/openscad_cpp_evaluator/__init__.py +41 -4
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/function_builtins.cpp +1 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/import.cpp +50 -2
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/registry.cpp +5 -2
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/bytecode_vm.cpp +2 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/csg_generate.cpp +14 -2
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/csg_resolve.cpp +3 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/import/svg_import.cpp +53 -2
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_dxf_svg_import.cpp +102 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_feature_detection.cpp +2 -1
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_python_bindings.py +170 -0
- openscad_cpp_evaluator-1.20.5/src/.DS_Store +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/README.md +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/coverage.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/format_closure.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/utf8.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_coverage.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_include_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/uv.lock +0 -0
|
@@ -1121,6 +1121,18 @@ grep for `ponytail:`.
|
|
|
1121
1121
|
`user_calls.cpp`'s `evalFunctionCall`, special-cased *before* the `isBuiltinFunctionName` gate
|
|
1122
1122
|
since `import` isn't in that table — returns a VNF `[[verts],[faces]]` for a mesh file, a Region
|
|
1123
1123
|
`[[[x,y],...],...]` for DXF/SVG, or the JSON file's content as native values).
|
|
1124
|
+
**SVG filtering**: `id=` selects one element by its `id` (upstream's own parameter, and
|
|
1125
|
+
matching a `<g>` takes everything under it); `class=` selects by one entry of an element's
|
|
1126
|
+
space-separated `class` list, which is **this port's addition** (`supported_feature("svg-class")`)
|
|
1127
|
+
because a class is how a drawing marks "every cut line" without naming each one. Both apply in
|
|
1128
|
+
the module *and* expression forms, enclosing transforms still apply to a selected element (it
|
|
1129
|
+
has to land where it does in the drawing), and **a filter that matches nothing imports nothing
|
|
1130
|
+
and warns** rather than falling back to the whole file — a cut layer that quietly became every
|
|
1131
|
+
layer is the worst answer available. `layer=` stays DXF-only on purpose: upstream reuses the
|
|
1132
|
+
name for SVG but reads only Inkscape's `inkscape:label`, a vendor convention rather than
|
|
1133
|
+
anything SVG defines, so it cannot match a layer from any other tool. `id` had been in
|
|
1134
|
+
`registry.cpp`'s declared parameter list since long before anything read it, so a script
|
|
1135
|
+
filtering by id was accepted in full and silently handed the whole drawing.
|
|
1124
1136
|
`resolveFilePath()` (also declared here, defined in import.cpp) — resolves a file argument
|
|
1125
1137
|
relative to the *source .scad file's own directory* — is shared by every file-reading builtin,
|
|
1126
1138
|
not just import() (`surface.cpp` uses it too). `zip_stored.hpp` is a hand-rolled, dependency-free
|
|
@@ -1609,8 +1621,46 @@ implementation of "CSG subtree → `object()`"; both engines call it — the
|
|
|
1609
1621
|
interpreter from `evalRenderExpr`, the VM from `Op::PopBuiltinWrap`'s
|
|
1610
1622
|
`Kind::Measure` branch.
|
|
1611
1623
|
|
|
1624
|
+
**`idToCallChain` is what a picker should actually walk.** `idToNode` names the
|
|
1625
|
+
node that *produced* the geometry, and for anything a library builds that is a
|
|
1626
|
+
node inside the library — a plain `cube(10)` maps into BOSL2's `builtins.scad`
|
|
1627
|
+
the moment BOSL2 is included, because BOSL2 overrides the primitives with its
|
|
1628
|
+
own modules. Pointing an editor at that span means pointing it at another file,
|
|
1629
|
+
or (if the consumer forgets to check `origin`) splicing a library's byte offsets
|
|
1630
|
+
into the user's buffer, which is how BelfrySCAD #450 appended a `translate()` to
|
|
1631
|
+
the end of a 59-character script.
|
|
1632
|
+
|
|
1633
|
+
`idToCallChain` maps each `originalID` to its **whole call chain**, innermost
|
|
1634
|
+
frame first. Deliberately unfiltered: a single `cuboid()` is 24 frames, most of
|
|
1635
|
+
them BOSL2's own `attachable`/`_attach_transform` layers, and someone debugging
|
|
1636
|
+
the library wants to step into exactly those. Which frames are reachable depends
|
|
1637
|
+
on what the front end has open, which the evaluator has no business guessing —
|
|
1638
|
+
so it records all of them and a consumer picks its level (BelfrySCAD defaults to
|
|
1639
|
+
the last frame in the running script).
|
|
1640
|
+
|
|
1641
|
+
It is stored as a **cactus stack**, not a list per node. Chains nest, so the
|
|
1642
|
+
distinct chains over a run form a tree: `callChains_` holds `{site, parent,
|
|
1643
|
+
isModule}` and a `CSGNode` holds one `uint32` into it. Memory tracks distinct
|
|
1644
|
+
call *paths* — tens on a real model; Dalek's 139 bodies share 13 innermost sites
|
|
1645
|
+
— rather than CSG nodes, and nothing allocates per node. That is what makes this
|
|
1646
|
+
affordable where "the full frame list a TRACE would need" (`csg_node.hpp`) was
|
|
1647
|
+
not.
|
|
1648
|
+
|
|
1649
|
+
`isModule` separates a module frame, which has geometry behind it, from a
|
|
1650
|
+
function frame like `_find_anchor`, which does not: both are worth showing, only
|
|
1651
|
+
the former is worth dragging.
|
|
1652
|
+
|
|
1653
|
+
A cache hit restamps to the chain reaching the geometry *now*, not the one that
|
|
1654
|
+
first produced it: a module called twice genuinely is two call chains.
|
|
1655
|
+
|
|
1656
|
+
No stored script path is needed to tell the user's file from a library:
|
|
1657
|
+
`callStack_.front()` is by construction the call made from top level.
|
|
1658
|
+
|
|
1659
|
+
Exposed to Python as `node.call_sites` (innermost-first `_CallFrame`s, each a
|
|
1660
|
+
`_Position` plus `is_module`), with `node.call_site` as the innermost.
|
|
1661
|
+
|
|
1612
1662
|
`Evaluator::measuring_` is set for the whole generate. It suppresses the four
|
|
1613
|
-
writes that exist solely to describe *drawn* geometry — `idToNode`/`idToColor` in
|
|
1663
|
+
writes that exist solely to describe *drawn* geometry — `idToNode`/`idToColor`/`idToCallChain` in
|
|
1614
1664
|
`tagGenerated` and `tagDisplayOnly`, the `restampCachedIds` call on a cache hit,
|
|
1615
1665
|
and `cacheProducer_` — because those tables are cleared once per pass, so a leak
|
|
1616
1666
|
is permanent and surfaces later as wrong click-to-source. It also suppresses
|
|
@@ -196,13 +196,37 @@ struct IdSpan {
|
|
|
196
196
|
uint32_t id;
|
|
197
197
|
int start, end, line, column;
|
|
198
198
|
std::string origin;
|
|
199
|
+
// The whole call chain behind this body, innermost frame first. Empty
|
|
200
|
+
// for geometry written at top level, where the span above is already
|
|
201
|
+
// the user's own source. Library frames included on purpose -- a BOSL2
|
|
202
|
+
// author stepping into `attachable` wants them; a front end picks the
|
|
203
|
+
// level it can actually show. See Evaluator::idToCallChain.
|
|
204
|
+
struct Frame {
|
|
205
|
+
int start, end, line, column;
|
|
206
|
+
std::string origin;
|
|
207
|
+
bool is_module;
|
|
208
|
+
};
|
|
209
|
+
std::vector<Frame> chain;
|
|
199
210
|
};
|
|
200
211
|
|
|
201
212
|
void collectIdSpans(const oscadeval::Evaluator& ev, std::vector<IdSpan>& out) {
|
|
202
213
|
out.reserve(ev.idToNode.size());
|
|
203
214
|
for (const auto& [id, node] : ev.idToNode) {
|
|
204
215
|
const oscad::Position& p = node->position();
|
|
205
|
-
|
|
216
|
+
IdSpan s{id, p.start_offset, p.end_offset, p.line, p.column, p.origin};
|
|
217
|
+
auto found = ev.idToCallChain.find(id);
|
|
218
|
+
uint32_t idx = found == ev.idToCallChain.end()
|
|
219
|
+
? oscadeval::Evaluator::kNoCallChain : found->second;
|
|
220
|
+
while (idx != oscadeval::Evaluator::kNoCallChain && idx < ev.callChains_.size()) {
|
|
221
|
+
const auto& e = ev.callChains_[idx];
|
|
222
|
+
if (e.site) {
|
|
223
|
+
s.chain.push_back({e.site->start_offset, e.site->end_offset,
|
|
224
|
+
e.site->line, e.site->column, e.site->origin,
|
|
225
|
+
e.isModule});
|
|
226
|
+
}
|
|
227
|
+
idx = e.parent;
|
|
228
|
+
}
|
|
229
|
+
out.push_back(std::move(s));
|
|
206
230
|
}
|
|
207
231
|
}
|
|
208
232
|
|
|
@@ -219,8 +243,12 @@ nb::list bodiesToList(std::vector<oscadeval::ColoredBody>& bodies) {
|
|
|
219
243
|
|
|
220
244
|
nb::dict idSpansToDict(const std::vector<IdSpan>& idSpans) {
|
|
221
245
|
nb::dict d;
|
|
222
|
-
for (const IdSpan& s : idSpans)
|
|
223
|
-
|
|
246
|
+
for (const IdSpan& s : idSpans) {
|
|
247
|
+
nb::list chain;
|
|
248
|
+
for (const IdSpan::Frame& f : s.chain)
|
|
249
|
+
chain.append(nb::make_tuple(f.start, f.end, f.line, f.column, f.origin, f.is_module));
|
|
250
|
+
d[nb::cast(s.id)] = nb::make_tuple(s.start, s.end, s.line, s.column, s.origin, chain);
|
|
251
|
+
}
|
|
224
252
|
return d;
|
|
225
253
|
}
|
|
226
254
|
|
|
@@ -51,6 +51,13 @@ struct CSGNode {
|
|
|
51
51
|
// call sites still share a cache entry.
|
|
52
52
|
const oscad::Position* warnEntry = nullptr;
|
|
53
53
|
|
|
54
|
+
// This node's interned call chain (Evaluator::callChains_), captured
|
|
55
|
+
// at RESOLVE time for the same reason warnEntry is: a picker runs long
|
|
56
|
+
// after the stack has unwound. kNoCallChain at top level. One uint32,
|
|
57
|
+
// not a frame list -- see Evaluator's call-chain section for why that
|
|
58
|
+
// is affordable where a per-node list was not.
|
|
59
|
+
uint32_t callChain = UINT32_MAX;
|
|
60
|
+
|
|
54
61
|
// 1 + the deepest child's own treeDepth (1 for a leaf) -- set once,
|
|
55
62
|
// at construction, by whichever csg_resolve.cpp site finalizes this
|
|
56
63
|
// node's own `children` (buildTreeNode, evalModularCall's non-splice
|
|
@@ -22,6 +22,20 @@ std::vector<Contour2d> loadDxfContours(const std::string& path, const std::optio
|
|
|
22
22
|
// group transforms). Y is flipped (SVG's is down, OpenSCAD's is up).
|
|
23
23
|
// Throws std::runtime_error on I/O/parse failure. Mirrors
|
|
24
24
|
// _load_svg_contours.
|
|
25
|
-
|
|
25
|
+
//: Which part of the drawing to import. Both unset means the whole file.
|
|
26
|
+
//: `id` matches an element's `id` attribute; `cls` matches one entry of its
|
|
27
|
+
//: space-separated `class` list. Matching an element takes everything under
|
|
28
|
+
//: it, so a filter naming a <g> means "that group".
|
|
29
|
+
struct SvgFilter {
|
|
30
|
+
std::optional<std::string> id;
|
|
31
|
+
std::optional<std::string> cls;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// `matched`, when given, reports whether the filter found anything. A miss
|
|
35
|
+
// imports nothing rather than falling back to the whole drawing; the caller
|
|
36
|
+
// warns. Always true when no filter was set.
|
|
37
|
+
std::vector<Contour2d> loadSvgContours(const std::string& path,
|
|
38
|
+
const SvgFilter& filter = {},
|
|
39
|
+
bool* matched = nullptr);
|
|
26
40
|
|
|
27
41
|
} // namespace oscadeval
|
|
@@ -284,9 +284,21 @@ public:
|
|
|
284
284
|
// Give cached bodies fresh originalIDs so a second call site reusing
|
|
285
285
|
// them is not confused with the first. See its definition.
|
|
286
286
|
void restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::ASTNode& node,
|
|
287
|
-
const oscad::ASTNode* producer);
|
|
287
|
+
const oscad::ASTNode* producer, uint32_t callChain);
|
|
288
288
|
|
|
289
289
|
std::unordered_map<uint32_t, const oscad::ASTNode*> idToNode;
|
|
290
|
+
// originalID -> its interned call chain (innermost frame; walk
|
|
291
|
+
// callChains_[i].parent outwards), or kNoCallChain for geometry
|
|
292
|
+
// written at top level, where idToNode already is the user's node.
|
|
293
|
+
//
|
|
294
|
+
// idToNode names the node that PRODUCED the geometry, which for
|
|
295
|
+
// anything a library builds is a node inside that library -- including
|
|
296
|
+
// a plain `cube(10)` once BOSL2 is included, since BOSL2 overrides the
|
|
297
|
+
// primitives with its own modules. A consumer that wants to point an
|
|
298
|
+
// editor at a line the user can actually see walks this instead, and
|
|
299
|
+
// picks the level it wants: the last frame in the script for an
|
|
300
|
+
// ordinary pick, deeper for someone debugging the library itself.
|
|
301
|
+
std::unordered_map<uint32_t, uint32_t> idToCallChain;
|
|
290
302
|
// Nodes whose bodies were actually generated this run (not served from
|
|
291
303
|
// the ManifoldCache). Reset per run; a diagnostic, and the only honest
|
|
292
304
|
// way for a test to tell a cache hit from a miss now that an inner
|
|
@@ -327,6 +339,57 @@ public:
|
|
|
327
339
|
return callStack_.empty() ? nullptr : callStack_.front().callPosition;
|
|
328
340
|
}
|
|
329
341
|
|
|
342
|
+
// -- Call chains -----------------------------------------------------
|
|
343
|
+
//
|
|
344
|
+
// The whole call chain behind a body, so a picker can step through it.
|
|
345
|
+
// Deliberately UNFILTERED: which frames are reachable depends on what
|
|
346
|
+
// the front end has open, which the evaluator has no business guessing.
|
|
347
|
+
// A BOSL2 author stepping into `attachable` -> `_attach_transform` is
|
|
348
|
+
// the case that settles it; a single cuboid() call is seven frames, six
|
|
349
|
+
// of them inside the library.
|
|
350
|
+
//
|
|
351
|
+
// Stored as a cactus stack rather than a list per node. Call chains
|
|
352
|
+
// nest, so the distinct chains over a run form a TREE: each entry is
|
|
353
|
+
// one frame plus its parent's index, and a CSGNode holds a single
|
|
354
|
+
// uint32 into the pool. Memory tracks distinct call PATHS (tens on a
|
|
355
|
+
// real model -- Dalek's 139 bodies share 13 innermost sites) rather
|
|
356
|
+
// than CSG nodes, and nothing allocates per node. That is what makes
|
|
357
|
+
// this affordable where "the full frame list a TRACE would need"
|
|
358
|
+
// (csg_node.hpp) was not.
|
|
359
|
+
static constexpr uint32_t kNoCallChain = UINT32_MAX;
|
|
360
|
+
|
|
361
|
+
struct CallChainEntry {
|
|
362
|
+
const oscad::Position* site = nullptr; // non-owning, AST-lifetime-bound
|
|
363
|
+
uint32_t parent = kNoCallChain; // next frame OUT, or kNoCallChain
|
|
364
|
+
bool isModule = false; // a function frame has no geometry to drag
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// Intern callStack_ as it stands and return its index, or kNoCallChain
|
|
368
|
+
// at top level. Outermost-first walk, so a chain sharing a prefix with
|
|
369
|
+
// one already interned costs only its own tail.
|
|
370
|
+
uint32_t internCurrentCallChain() {
|
|
371
|
+
uint32_t parent = kNoCallChain;
|
|
372
|
+
for (const CallStackFrame& f : callStack_) {
|
|
373
|
+
if (!f.callPosition) continue;
|
|
374
|
+
const ChainKey key{parent, f.callPosition};
|
|
375
|
+
auto found = chainIndex_.find(key);
|
|
376
|
+
if (found != chainIndex_.end()) {
|
|
377
|
+
parent = found->second;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
const uint32_t idx = static_cast<uint32_t>(callChains_.size());
|
|
381
|
+
callChains_.push_back({f.callPosition, parent,
|
|
382
|
+
f.kind == CallStackFrame::Kind::Module});
|
|
383
|
+
chainIndex_.emplace(key, idx);
|
|
384
|
+
parent = idx;
|
|
385
|
+
}
|
|
386
|
+
return parent; // the innermost frame, or kNoCallChain
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// The interned pool. Public for the same reason idToNode is: a binding
|
|
390
|
+
// walks it after evaluate() to flatten each id's chain.
|
|
391
|
+
std::vector<CallChainEntry> callChains_;
|
|
392
|
+
|
|
330
393
|
// Set by generateTreeImpl() to the CSGNode currently being generated,
|
|
331
394
|
// so warn() can name the user's own call site during a phase where
|
|
332
395
|
// callStack_ is necessarily empty. Public for the same reason
|
|
@@ -335,6 +398,9 @@ public:
|
|
|
335
398
|
// than anything reentrancy-aware, since generateTreeImpl recurses
|
|
336
399
|
// depth-first on one thread.
|
|
337
400
|
const oscad::Position* generateWarnEntry = nullptr;
|
|
401
|
+
// The same republish for CSGNode::callChain, read by tagGenerated to
|
|
402
|
+
// fill idToCallChain.
|
|
403
|
+
uint32_t generateCallChain = kNoCallChain;
|
|
338
404
|
|
|
339
405
|
// Set while generating anything beneath a hull(). An open mesh there is
|
|
340
406
|
// not a mistake to report: a convex hull needs only points, so BOSL2's
|
|
@@ -1790,6 +1856,22 @@ private:
|
|
|
1790
1856
|
// eval_error.hpp).
|
|
1791
1857
|
std::vector<CallStackFrame> callStack_;
|
|
1792
1858
|
|
|
1859
|
+
// (parent chain index, frame position) -> chain index, the memo that
|
|
1860
|
+
// makes internCurrentCallChain() share prefixes. Cleared with the pool
|
|
1861
|
+
// wherever idToNode is.
|
|
1862
|
+
struct ChainKey {
|
|
1863
|
+
uint32_t parent;
|
|
1864
|
+
const oscad::Position* site;
|
|
1865
|
+
bool operator==(const ChainKey& o) const { return parent == o.parent && site == o.site; }
|
|
1866
|
+
};
|
|
1867
|
+
struct ChainKeyHash {
|
|
1868
|
+
std::size_t operator()(const ChainKey& k) const {
|
|
1869
|
+
return std::hash<uint32_t>{}(k.parent) ^
|
|
1870
|
+
(std::hash<const void*>{}(k.site) << 1);
|
|
1871
|
+
}
|
|
1872
|
+
};
|
|
1873
|
+
std::unordered_map<ChainKey, uint32_t, ChainKeyHash> chainIndex_;
|
|
1874
|
+
|
|
1793
1875
|
// Count of callStack_ entries that ACTUALLY cost native C++ stack --
|
|
1794
1876
|
// i.e. pushed with skipDepthGuard=false (enterUserCall's own
|
|
1795
1877
|
// interpreted-call sites: evalUserFunctionCore, evalUserModule).
|
|
@@ -47,11 +47,43 @@ class _Position:
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
class _DeclNode:
|
|
50
|
-
__slots__ = ("name", "position")
|
|
50
|
+
__slots__ = ("name", "position", "call_sites")
|
|
51
51
|
|
|
52
|
-
def __init__(self, name, position):
|
|
52
|
+
def __init__(self, name, position, call_sites=()):
|
|
53
53
|
self.name = name
|
|
54
54
|
self.position = position
|
|
55
|
+
#: For a geometry node: the whole call chain behind it, INNERMOST
|
|
56
|
+
#: frame first, each a `_CallFrame`. Empty for geometry written at
|
|
57
|
+
#: top level, where `position` is already the user's own source.
|
|
58
|
+
#:
|
|
59
|
+
#: `position` names the node that PRODUCED the geometry, which for
|
|
60
|
+
#: anything a library builds is inside that library -- a plain
|
|
61
|
+
#: `cube(10)` lands in BOSL2's builtins.scad once BOSL2 is included.
|
|
62
|
+
#: A caller that wants a line to show the user walks this and picks
|
|
63
|
+
#: the level it can display: the last frame in the running script
|
|
64
|
+
#: for an ordinary pick, a deeper one for someone reading the
|
|
65
|
+
#: library itself. Library frames are included deliberately.
|
|
66
|
+
self.call_sites = tuple(call_sites)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def call_site(self):
|
|
70
|
+
"""The innermost frame, or None. Convenience for a caller that does
|
|
71
|
+
not care about the chain."""
|
|
72
|
+
return self.call_sites[0] if self.call_sites else None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class _CallFrame(_Position):
|
|
76
|
+
"""One call in a chain: a position, plus whether it is a MODULE call.
|
|
77
|
+
|
|
78
|
+
A function frame (`_find_anchor`, an `assert`) has no geometry of its
|
|
79
|
+
own to drag, so a gizmo should decline it even though it is worth
|
|
80
|
+
showing.
|
|
81
|
+
"""
|
|
82
|
+
__slots__ = ("is_module",)
|
|
83
|
+
|
|
84
|
+
def __init__(self, line, column, origin, start_offset, end_offset, is_module):
|
|
85
|
+
super().__init__(line, column, origin, start_offset, end_offset)
|
|
86
|
+
self.is_module = is_module
|
|
55
87
|
|
|
56
88
|
|
|
57
89
|
class OscObject:
|
|
@@ -635,8 +667,13 @@ class Evaluator:
|
|
|
635
667
|
# originalID -> a node with `.position` (start/end offsets) for WYSIWYG
|
|
636
668
|
# picking and gizmo write-back.
|
|
637
669
|
id_to_node = {
|
|
638
|
-
oid: _DeclNode(
|
|
639
|
-
|
|
670
|
+
oid: _DeclNode(
|
|
671
|
+
None,
|
|
672
|
+
_Position(line, column, origin, start, end),
|
|
673
|
+
[_CallFrame(cl, cc, co, cs, ce, mod)
|
|
674
|
+
for (cs, ce, cl, cc, co, mod) in chain],
|
|
675
|
+
)
|
|
676
|
+
for oid, (start, end, line, column, origin, chain) in id_spans.items()
|
|
640
677
|
}
|
|
641
678
|
return bodies, id_to_node
|
|
642
679
|
|
{openscad_cpp_evaluator-1.20.5 → openscad_cpp_evaluator-1.22.0}/src/builtins/function_builtins.cpp
RENAMED
|
@@ -1046,6 +1046,7 @@ const std::unordered_map<std::string, double>& featureLevels() {
|
|
|
1046
1046
|
{"expr-import", 1.0}, // import() in expression position
|
|
1047
1047
|
{"object-function", 1.0}, // object(), unconditional here
|
|
1048
1048
|
{"roof-op", 1.0}, // roof(), method="voronoi" only
|
|
1049
|
+
{"svg-class", 1.0}, // import(svg, class=); id= is upstream's
|
|
1049
1050
|
};
|
|
1050
1051
|
return levels;
|
|
1051
1052
|
}
|
|
@@ -176,6 +176,13 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
|
|
|
176
176
|
auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
|
|
177
177
|
const Value fileArg = getArg(args, 0, "file", Value{});
|
|
178
178
|
const Value layerArg = getArg(args, std::nullopt, "layer", Value{});
|
|
179
|
+
// SVG only. `id` matches an element's id; `class` matches one entry of
|
|
180
|
+
// its space-separated class list. `layer` stays DXF's, where layers are
|
|
181
|
+
// part of the format -- upstream reuses the name for SVG but reads only
|
|
182
|
+
// Inkscape's `inkscape:label`, which is a vendor convention rather than
|
|
183
|
+
// anything SVG defines.
|
|
184
|
+
const Value idArg = getArg(args, std::nullopt, "id", Value{});
|
|
185
|
+
const Value classArg = getArg(args, std::nullopt, "class", Value{});
|
|
179
186
|
// Not an OpenSCAD parameter. A script using it will not run upstream,
|
|
180
187
|
// which is why it has to be asked for rather than being the default.
|
|
181
188
|
const bool repair = truthy(getArg(args, std::nullopt, "repair", Value{false}));
|
|
@@ -196,7 +203,24 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
|
|
|
196
203
|
if (const std::string* s = std::get_if<std::string>(&layerArg)) layer = *s;
|
|
197
204
|
contours = loadDxfContours(path, layer);
|
|
198
205
|
} else {
|
|
199
|
-
|
|
206
|
+
SvgFilter filter;
|
|
207
|
+
if (const std::string* v = std::get_if<std::string>(&idArg)) filter.id = *v;
|
|
208
|
+
if (const std::string* v = std::get_if<std::string>(&classArg)) filter.cls = *v;
|
|
209
|
+
bool matched = true;
|
|
210
|
+
contours = loadSvgContours(path, filter, &matched);
|
|
211
|
+
if (!matched) {
|
|
212
|
+
// Nothing imported, rather than silently falling back to
|
|
213
|
+
// the whole drawing -- a cut layer that quietly became
|
|
214
|
+
// every layer is the worst answer available.
|
|
215
|
+
std::string what;
|
|
216
|
+
if (filter.id) what += "id = \"" + *filter.id + "\"";
|
|
217
|
+
if (filter.cls) {
|
|
218
|
+
if (!what.empty()) what += ", ";
|
|
219
|
+
what += "class = \"" + *filter.cls + "\"";
|
|
220
|
+
}
|
|
221
|
+
ev.warn("import() filter " + what + " did not match anything in "
|
|
222
|
+
+ path, &node.position());
|
|
223
|
+
}
|
|
200
224
|
}
|
|
201
225
|
} catch (const std::exception& e) {
|
|
202
226
|
ev.error(std::string("import: ") + e.what(), node);
|
|
@@ -340,6 +364,13 @@ std::vector<ColoredBody> generateImport(Evaluator& ev, const CSGParams& params,
|
|
|
340
364
|
Value importAsValue(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& node) {
|
|
341
365
|
const Value fileArg = getArg(args, 0, "file", Value{});
|
|
342
366
|
const Value layerArg = getArg(args, std::nullopt, "layer", Value{});
|
|
367
|
+
// SVG only. `id` matches an element's id; `class` matches one entry of
|
|
368
|
+
// its space-separated class list. `layer` stays DXF's, where layers are
|
|
369
|
+
// part of the format -- upstream reuses the name for SVG but reads only
|
|
370
|
+
// Inkscape's `inkscape:label`, which is a vendor convention rather than
|
|
371
|
+
// anything SVG defines.
|
|
372
|
+
const Value idArg = getArg(args, std::nullopt, "id", Value{});
|
|
373
|
+
const Value classArg = getArg(args, std::nullopt, "class", Value{});
|
|
343
374
|
if (std::holds_alternative<std::monostate>(fileArg)) {
|
|
344
375
|
ev.error("import: 'file' parameter is required", node);
|
|
345
376
|
}
|
|
@@ -356,7 +387,24 @@ Value importAsValue(Evaluator& ev, const CallArgs& args, const oscad::ASTNode& n
|
|
|
356
387
|
if (const std::string* s = std::get_if<std::string>(&layerArg)) layer = *s;
|
|
357
388
|
contours = loadDxfContours(path, layer);
|
|
358
389
|
} else {
|
|
359
|
-
|
|
390
|
+
SvgFilter filter;
|
|
391
|
+
if (const std::string* v = std::get_if<std::string>(&idArg)) filter.id = *v;
|
|
392
|
+
if (const std::string* v = std::get_if<std::string>(&classArg)) filter.cls = *v;
|
|
393
|
+
bool matched = true;
|
|
394
|
+
contours = loadSvgContours(path, filter, &matched);
|
|
395
|
+
if (!matched) {
|
|
396
|
+
// Nothing imported, rather than silently falling back to
|
|
397
|
+
// the whole drawing -- a cut layer that quietly became
|
|
398
|
+
// every layer is the worst answer available.
|
|
399
|
+
std::string what;
|
|
400
|
+
if (filter.id) what += "id = \"" + *filter.id + "\"";
|
|
401
|
+
if (filter.cls) {
|
|
402
|
+
if (!what.empty()) what += ", ";
|
|
403
|
+
what += "class = \"" + *filter.cls + "\"";
|
|
404
|
+
}
|
|
405
|
+
ev.warn("import() filter " + what + " did not match anything in "
|
|
406
|
+
+ path, &node.position());
|
|
407
|
+
}
|
|
360
408
|
}
|
|
361
409
|
return contoursToValue(contours);
|
|
362
410
|
}
|
|
@@ -116,10 +116,13 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
|
|
|
116
116
|
{"minkowski", {"convexity"}},
|
|
117
117
|
{"children", {"index", "separate"}},
|
|
118
118
|
{"render", {"convexity"}},
|
|
119
|
-
// "repair"
|
|
119
|
+
// "repair" and "class" are this port's own additions, not upstream
|
|
120
|
+
// parameters. ("id" IS upstream's, and was declared here long before
|
|
121
|
+
// anything read it -- so a script filtering by id was accepted in
|
|
122
|
+
// full and quietly given the whole drawing.)
|
|
120
123
|
{"import",
|
|
121
124
|
{"file", "layer", "convexity", "origin", "scale", "width", "height", "filename", "layername", "center", "dpi",
|
|
122
|
-
"id", "repair"}},
|
|
125
|
+
"id", "class", "repair"}},
|
|
123
126
|
{"linear_extrude", {"height", "v", "scale", "center", "twist", "slices", "segments", "convexity"}},
|
|
124
127
|
{"rotate_extrude", {"angle", "start", "convexity"}},
|
|
125
128
|
{"projection", {"cut", "convexity"}},
|
|
@@ -1400,6 +1400,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
|
|
|
1400
1400
|
treeNode->node = site.node;
|
|
1401
1401
|
treeNode->isBuiltin = true;
|
|
1402
1402
|
treeNode->warnEntry = ev.currentWarnEntry();
|
|
1403
|
+
treeNode->callChain = ev.internCurrentCallChain();
|
|
1403
1404
|
treeNode->children = std::move(children);
|
|
1404
1405
|
treeNode->params = std::move(pending.params);
|
|
1405
1406
|
treeNode->uncacheable = uncacheable;
|
|
@@ -1503,6 +1504,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
|
|
|
1503
1504
|
treeNode->node = site.node;
|
|
1504
1505
|
treeNode->isBuiltin = true;
|
|
1505
1506
|
treeNode->warnEntry = ev.currentWarnEntry();
|
|
1507
|
+
treeNode->callChain = ev.internCurrentCallChain();
|
|
1506
1508
|
treeNode->children = std::move(children);
|
|
1507
1509
|
treeNode->params = std::move(params);
|
|
1508
1510
|
treeNode->uncacheable = uncacheable;
|
|
@@ -68,7 +68,8 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
|
|
|
68
68
|
if (node.node && !measuring_) {
|
|
69
69
|
auto producer = cacheProducer_.find(*key);
|
|
70
70
|
restampCachedIds(node.bodies, *node.node,
|
|
71
|
-
producer == cacheProducer_.end() ? nullptr : producer->second
|
|
71
|
+
producer == cacheProducer_.end() ? nullptr : producer->second,
|
|
72
|
+
node.callChain);
|
|
72
73
|
}
|
|
73
74
|
} else {
|
|
74
75
|
// Recurse into children first (bottom-up) -- populates each
|
|
@@ -95,9 +96,12 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
|
|
|
95
96
|
// can name that line. Restored after (rather than left set)
|
|
96
97
|
// so a sibling generated at top level doesn't inherit it.
|
|
97
98
|
const oscad::Position* savedWarnEntry = generateWarnEntry;
|
|
99
|
+
const uint32_t savedCallChain = generateCallChain;
|
|
98
100
|
generateWarnEntry = node.warnEntry;
|
|
101
|
+
generateCallChain = node.callChain;
|
|
99
102
|
node.bodies = it->second(*this, node.params, node.children, *node.node);
|
|
100
103
|
generateWarnEntry = savedWarnEntry;
|
|
104
|
+
generateCallChain = savedCallChain;
|
|
101
105
|
} else {
|
|
102
106
|
// No registered GenerateFn for this kind (a builtin with
|
|
103
107
|
// display-only semantics like render(), or a non-builtin
|
|
@@ -331,7 +335,7 @@ std::shared_ptr<const std::vector<ColoredBody>> remapParts(const std::vector<Col
|
|
|
331
335
|
} // namespace
|
|
332
336
|
|
|
333
337
|
void Evaluator::restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::ASTNode& node,
|
|
334
|
-
const oscad::ASTNode* producer) {
|
|
338
|
+
const oscad::ASTNode* producer, uint32_t callChain) {
|
|
335
339
|
// A cache hit hands back the geometry AND the originalIDs of whichever
|
|
336
340
|
// call site first produced it. Those IDs are provenance -- "which node
|
|
337
341
|
// made this" -- not content, so two identical shapes at two call sites
|
|
@@ -374,6 +378,12 @@ void Evaluator::restampCachedIds(std::vector<ColoredBody>& bodies, const oscad::
|
|
|
374
378
|
auto old = idToNode.find(id);
|
|
375
379
|
idToNode[fresh] =
|
|
376
380
|
(old != idToNode.end() && old->second != producer) ? old->second : &node;
|
|
381
|
+
// Unlike idToNode, this is the call site that reached the
|
|
382
|
+
// geometry NOW: whichever node the cached copy is being
|
|
383
|
+
// reused at is the line the user would be shown, whatever
|
|
384
|
+
// produced the original. A module called twice really is
|
|
385
|
+
// two different call sites.
|
|
386
|
+
idToCallChain[fresh] = callChain;
|
|
377
387
|
// The colour has to be re-recorded too, or a later merge
|
|
378
388
|
// cannot tell this body's runs apart from uncoloured ones
|
|
379
389
|
// (attachTriColors looks runs up here). Across renders
|
|
@@ -453,6 +463,7 @@ ColoredBody Evaluator::tagGenerated(manifold::Manifold body, const oscad::ASTNod
|
|
|
453
463
|
for (uint32_t originalId : mesh.runOriginalID) {
|
|
454
464
|
idToNode[originalId] = &node;
|
|
455
465
|
idToColor[originalId] = color;
|
|
466
|
+
idToCallChain[originalId] = generateCallChain;
|
|
456
467
|
}
|
|
457
468
|
}
|
|
458
469
|
ColoredBody cb;
|
|
@@ -474,6 +485,7 @@ ColoredBody Evaluator::tagDisplayOnly(manifold::MeshGL mesh, const oscad::ASTNod
|
|
|
474
485
|
if (!measuring_) {
|
|
475
486
|
idToNode[originalId] = &node;
|
|
476
487
|
idToColor[originalId] = color;
|
|
488
|
+
idToCallChain[originalId] = generateCallChain;
|
|
477
489
|
}
|
|
478
490
|
|
|
479
491
|
ColoredBody cb;
|
|
@@ -68,6 +68,7 @@ void Evaluator::buildTreeNode(const std::string& kind, const oscad::ASTNode& nod
|
|
|
68
68
|
treeNode->node = &node;
|
|
69
69
|
treeNode->isBuiltin = true;
|
|
70
70
|
treeNode->warnEntry = currentWarnEntry();
|
|
71
|
+
treeNode->callChain = internCurrentCallChain();
|
|
71
72
|
treeNode->children = std::move(children);
|
|
72
73
|
treeNode->params = std::move(params);
|
|
73
74
|
treeNode->uncacheable = uncacheable;
|
|
@@ -139,6 +140,7 @@ void Evaluator::evalModularCall(const oscad::ModularCall& node, EvalContext& ctx
|
|
|
139
140
|
treeNode->node = &node;
|
|
140
141
|
treeNode->isBuiltin = true;
|
|
141
142
|
treeNode->warnEntry = currentWarnEntry();
|
|
143
|
+
treeNode->callChain = internCurrentCallChain();
|
|
142
144
|
treeNode->uncacheable = uncacheable;
|
|
143
145
|
treeNode->children = std::move(children);
|
|
144
146
|
treeNode->params = std::move(params);
|
|
@@ -164,6 +166,7 @@ void Evaluator::spliceModuleChildren(std::vector<std::unique_ptr<CSGNode>> child
|
|
|
164
166
|
unionNode->node = &callNode;
|
|
165
167
|
unionNode->isBuiltin = false;
|
|
166
168
|
unionNode->warnEntry = currentWarnEntry();
|
|
169
|
+
unionNode->callChain = internCurrentCallChain();
|
|
167
170
|
unionNode->uncacheable = std::any_of(children.begin(), children.end(), [](const auto& c) { return c->uncacheable; });
|
|
168
171
|
unionNode->children = std::move(children);
|
|
169
172
|
setTreeDepthOrThrow(*unionNode, callNode);
|
|
@@ -450,9 +450,50 @@ void walk(const XmlNode& el, const Mat3& mat, std::vector<Contour2d>& out) {
|
|
|
450
450
|
for (const XmlNode& child : el.children) walk(child, m, out);
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
+
// `class` is a space-separated LIST, so this is membership rather than
|
|
454
|
+
// equality: class="cut outline" is matched by class="cut".
|
|
455
|
+
bool hasClass(const XmlNode& el, const std::string& want) {
|
|
456
|
+
const std::string attr = el.getAttr("class");
|
|
457
|
+
size_t i = 0;
|
|
458
|
+
while (i < attr.size()) {
|
|
459
|
+
while (i < attr.size() && std::isspace(static_cast<unsigned char>(attr[i]))) ++i;
|
|
460
|
+
size_t j = i;
|
|
461
|
+
while (j < attr.size() && !std::isspace(static_cast<unsigned char>(attr[j]))) ++j;
|
|
462
|
+
if (j > i && attr.compare(i, j - i, want) == 0) return true;
|
|
463
|
+
i = j;
|
|
464
|
+
}
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
bool matches(const XmlNode& el, const SvgFilter& f) {
|
|
469
|
+
if (f.id && el.getAttr("id") == *f.id) return true;
|
|
470
|
+
if (f.cls && hasClass(el, *f.cls)) return true;
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Walk looking for matching elements; everything under one is taken whole,
|
|
475
|
+
// which is what makes `id=` on a <g> mean "that group". Transforms above
|
|
476
|
+
// the match still apply -- a selected shape must land where it does in the
|
|
477
|
+
// drawing, not at the origin.
|
|
478
|
+
void walkFiltered(const XmlNode& el, const Mat3& mat, const SvgFilter& f,
|
|
479
|
+
std::vector<Contour2d>& out, bool& matched) {
|
|
480
|
+
if (el.tag == "defs" || el.tag == "symbol") return;
|
|
481
|
+
const Mat3 m = compose(parseTransform(el.getAttr("transform")), mat);
|
|
482
|
+
if (matches(el, f)) {
|
|
483
|
+
matched = true;
|
|
484
|
+
const std::vector<Contour2d> shapes = shapeContours(el, m);
|
|
485
|
+
out.insert(out.end(), shapes.begin(), shapes.end());
|
|
486
|
+
for (const XmlNode& child : el.children) walk(child, m, out);
|
|
487
|
+
return; // a match is taken whole; no nested re-matching
|
|
488
|
+
}
|
|
489
|
+
for (const XmlNode& child : el.children) walkFiltered(child, m, f, out, matched);
|
|
490
|
+
}
|
|
491
|
+
|
|
453
492
|
} // namespace
|
|
454
493
|
|
|
455
|
-
std::vector<Contour2d> loadSvgContours(const std::string& path
|
|
494
|
+
std::vector<Contour2d> loadSvgContours(const std::string& path,
|
|
495
|
+
const SvgFilter& filter,
|
|
496
|
+
bool* matched) {
|
|
456
497
|
std::ifstream in(path);
|
|
457
498
|
if (!in) throw std::runtime_error("could not open '" + path + "'");
|
|
458
499
|
std::stringstream buf;
|
|
@@ -461,7 +502,17 @@ std::vector<Contour2d> loadSvgContours(const std::string& path) {
|
|
|
461
502
|
if (!root) throw std::runtime_error("'" + path + "' is not a well-formed SVG file");
|
|
462
503
|
|
|
463
504
|
std::vector<Contour2d> out;
|
|
464
|
-
|
|
505
|
+
if (!filter.id && !filter.cls) {
|
|
506
|
+
if (matched) *matched = true; // no filter, nothing to miss
|
|
507
|
+
walk(*root, Mat3{}, out);
|
|
508
|
+
return out;
|
|
509
|
+
}
|
|
510
|
+
bool hit = false;
|
|
511
|
+
walkFiltered(*root, Mat3{}, filter, out, hit);
|
|
512
|
+
// A filter that matched nothing imports nothing -- it does NOT fall
|
|
513
|
+
// back to the whole drawing, which would be the silent-wrong-geometry
|
|
514
|
+
// answer. The caller turns this into the warning.
|
|
515
|
+
if (matched) *matched = hit;
|
|
465
516
|
return out;
|
|
466
517
|
}
|
|
467
518
|
|